@wealthx/shadcn 1.0.2 → 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 -138
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +82 -0
  4. package/dist/{chunk-6OJF6XRN.mjs → chunk-24FUO7TD.mjs} +4 -8
  5. package/dist/{chunk-4AJ5HWHD.mjs → chunk-2I5S2AMY.mjs} +3 -3
  6. package/dist/chunk-2SF672SZ.mjs +161 -0
  7. package/dist/{chunk-GPRJQ24C.mjs → chunk-34NWQURD.mjs} +2 -2
  8. package/dist/{chunk-MQ72DIBH.mjs → chunk-3GF7OVTP.mjs} +14 -5
  9. package/dist/chunk-3WMX6KWS.mjs +245 -0
  10. package/dist/{chunk-PMKODV6M.mjs → chunk-462HMNO4.mjs} +6 -10
  11. package/dist/chunk-4CX4SBRO.mjs +153 -0
  12. package/dist/chunk-4MN6UQHG.mjs +443 -0
  13. package/dist/chunk-5QQVZTVZ.mjs +233 -0
  14. package/dist/{chunk-BGP2N52Z.mjs → chunk-66MI7Q4B.mjs} +5 -5
  15. package/dist/chunk-6FCGKSZX.mjs +268 -0
  16. package/dist/{chunk-CGOKTPXU.mjs → chunk-6JQFUE5I.mjs} +20 -23
  17. package/dist/{chunk-Z3MK2KKZ.mjs → chunk-7DHU4VGG.mjs} +7 -3
  18. package/dist/{chunk-VZ2NR7L3.mjs → chunk-7PYJD5JI.mjs} +35 -27
  19. package/dist/{chunk-JU2RUWHF.mjs → chunk-7XJHLGUV.mjs} +1 -1
  20. package/dist/{chunk-BMFN37JH.mjs → chunk-7YAU5CY6.mjs} +1 -1
  21. package/dist/chunk-A56YQQHG.mjs +402 -0
  22. package/dist/chunk-AH52LG6N.mjs +315 -0
  23. package/dist/{chunk-SLWCCURD.mjs → chunk-CLIN5525.mjs} +8 -4
  24. package/dist/{chunk-3VQNJ235.mjs → chunk-CSDO6VBW.mjs} +7 -0
  25. package/dist/chunk-D4ILTPOG.mjs +293 -0
  26. package/dist/{chunk-HS7TFG7V.mjs → chunk-D6ID6M4V.mjs} +1 -1
  27. package/dist/chunk-DOH3EHX7.mjs +378 -0
  28. package/dist/{chunk-MJIEMGRD.mjs → chunk-EFRENWEJ.mjs} +9 -17
  29. package/dist/chunk-ERGGHC2V.mjs +185 -0
  30. package/dist/{chunk-OXQQNQZI.mjs → chunk-FEZKMUCF.mjs} +10 -1
  31. package/dist/{chunk-55CEW76V.mjs → chunk-FH6QVUVZ.mjs} +1 -1
  32. package/dist/chunk-FMAXJ2SI.mjs +71 -0
  33. package/dist/chunk-FZIXGLMV.mjs +173 -0
  34. package/dist/{chunk-DS2AMHN2.mjs → chunk-GYMYRIZP.mjs} +2 -2
  35. package/dist/{chunk-KQDD5MU3.mjs → chunk-H45TKD34.mjs} +5 -5
  36. package/dist/{chunk-BBJBJSXQ.mjs → chunk-J5UICVJS.mjs} +1 -1
  37. package/dist/{chunk-RL772EH7.mjs → chunk-JHJHG4GO.mjs} +4 -12
  38. package/dist/chunk-KMCGSZTX.mjs +177 -0
  39. package/dist/{chunk-FHNT55I5.mjs → chunk-KUDCQ4FI.mjs} +4 -4
  40. package/dist/chunk-LE6YFY6D.mjs +209 -0
  41. package/dist/{chunk-HUVTPUV2.mjs → chunk-LLVQKSU3.mjs} +23 -19
  42. package/dist/{chunk-KKHTJNMM.mjs → chunk-MARPPFOJ.mjs} +8 -4
  43. package/dist/{chunk-6AFMNC42.mjs → chunk-N2PT566P.mjs} +15 -11
  44. package/dist/chunk-NLCKVHWB.mjs +161 -0
  45. package/dist/{chunk-YN5SYTOO.mjs → chunk-NQPOYKAQ.mjs} +9 -5
  46. package/dist/{chunk-ZZV5JVNW.mjs → chunk-NSLMILBT.mjs} +3 -7
  47. package/dist/chunk-NXA3CZ7A.mjs +248 -0
  48. package/dist/chunk-OGOYQ7BG.mjs +150 -0
  49. package/dist/{chunk-3NQGYJEZ.mjs → chunk-P6AM5V7O.mjs} +10 -18
  50. package/dist/{chunk-CZ3BW5GL.mjs → chunk-P76HMUI6.mjs} +5 -11
  51. package/dist/chunk-PCPLO5HT.mjs +671 -0
  52. package/dist/chunk-PG6K5XEC.mjs +475 -0
  53. package/dist/chunk-PJHPSRYD.mjs +234 -0
  54. package/dist/{chunk-DDPA2XXS.mjs → chunk-PMB3A7V3.mjs} +2 -2
  55. package/dist/chunk-PR6V5XKM.mjs +209 -0
  56. package/dist/{chunk-46OFHMQA.mjs → chunk-Q76O3RIQ.mjs} +10 -6
  57. package/dist/chunk-QVKWW6KE.mjs +272 -0
  58. package/dist/chunk-RGU7HOEC.mjs +140 -0
  59. package/dist/{chunk-JF4PHPD5.mjs → chunk-RGVKLTLH.mjs} +4 -4
  60. package/dist/{chunk-VG6UF6UT.mjs → chunk-RP3SQYA3.mjs} +2 -2
  61. package/dist/chunk-RRBS6D63.mjs +163 -0
  62. package/dist/chunk-SMQ3DG25.mjs +285 -0
  63. package/dist/chunk-SPJ5KXW7.mjs +199 -0
  64. package/dist/chunk-SYOD63OZ.mjs +225 -0
  65. package/dist/chunk-UFYSFDER.mjs +42 -0
  66. package/dist/chunk-VACKZOMY.mjs +190 -0
  67. package/dist/chunk-VLQZANBF.mjs +42 -0
  68. package/dist/chunk-WA6O6EUR.mjs +1885 -0
  69. package/dist/{chunk-E3K6O4FZ.mjs → chunk-WAZD7NFU.mjs} +5 -2
  70. package/dist/chunk-WG6JGJXB.mjs +165 -0
  71. package/dist/{chunk-I64K754C.mjs → chunk-WNGWBVLV.mjs} +2 -2
  72. package/dist/{chunk-3U7SD3MS.mjs → chunk-WOEHFRGB.mjs} +3 -3
  73. package/dist/{chunk-DKZRJOMF.mjs → chunk-XIRTEFKH.mjs} +12 -12
  74. package/dist/chunk-Y6DWJSKZ.mjs +79 -0
  75. package/dist/chunk-YKPROFLB.mjs +161 -0
  76. package/dist/{chunk-K76E2TQU.mjs → chunk-ZRO5JO3H.mjs} +107 -67
  77. package/dist/{chunk-VYMHBV6D.mjs → chunk-ZU4NV6RG.mjs} +5 -3
  78. package/dist/components/ui/accordion.js +40 -4
  79. package/dist/components/ui/accordion.mjs +2 -2
  80. package/dist/components/ui/add-column-modal.js +789 -0
  81. package/dist/components/ui/add-column-modal.mjs +17 -0
  82. package/dist/components/ui/add-lead-modal.js +647 -0
  83. package/dist/components/ui/add-lead-modal.mjs +16 -0
  84. package/dist/components/ui/ai-assistant-drawer.js +686 -0
  85. package/dist/components/ui/ai-assistant-drawer.mjs +16 -0
  86. package/dist/components/ui/alert-dialog.js +37 -5
  87. package/dist/components/ui/alert-dialog.mjs +4 -4
  88. package/dist/components/ui/alert.js +37 -11
  89. package/dist/components/ui/alert.mjs +2 -2
  90. package/dist/components/ui/avatar.js +36 -8
  91. package/dist/components/ui/avatar.mjs +2 -2
  92. package/dist/components/ui/backoffice-alert-history-chart.js +624 -0
  93. package/dist/components/ui/backoffice-alert-history-chart.mjs +16 -0
  94. package/dist/components/ui/backoffice-contact-history-chart.js +687 -0
  95. package/dist/components/ui/backoffice-contact-history-chart.mjs +16 -0
  96. package/dist/components/ui/badge.js +37 -2
  97. package/dist/components/ui/badge.mjs +2 -2
  98. package/dist/components/ui/borrowing-capacity-line-chart.js +639 -0
  99. package/dist/components/ui/borrowing-capacity-line-chart.mjs +16 -0
  100. package/dist/components/ui/button.js +35 -3
  101. package/dist/components/ui/button.mjs +2 -2
  102. package/dist/components/ui/calendar.js +43 -19
  103. package/dist/components/ui/calendar.mjs +3 -3
  104. package/dist/components/ui/card.js +40 -4
  105. package/dist/components/ui/card.mjs +2 -2
  106. package/dist/components/ui/cash-balance-line-chart.js +627 -0
  107. package/dist/components/ui/cash-balance-line-chart.mjs +16 -0
  108. package/dist/components/ui/cashflow-bar-chart.js +650 -0
  109. package/dist/components/ui/cashflow-bar-chart.mjs +16 -0
  110. package/dist/components/ui/checkbox.js +36 -5
  111. package/dist/components/ui/checkbox.mjs +2 -3
  112. package/dist/components/ui/chip.js +37 -2
  113. package/dist/components/ui/chip.mjs +3 -3
  114. package/dist/components/ui/combobox.js +280 -0
  115. package/dist/components/ui/combobox.mjs +28 -0
  116. package/dist/components/ui/data-table.js +160 -88
  117. package/dist/components/ui/data-table.mjs +10 -11
  118. package/dist/components/ui/date-picker.js +44 -20
  119. package/dist/components/ui/date-picker.mjs +6 -7
  120. package/dist/components/ui/dialog.js +44 -12
  121. package/dist/components/ui/dialog.mjs +4 -4
  122. package/dist/components/ui/drawer.js +46 -10
  123. package/dist/components/ui/drawer.mjs +3 -3
  124. package/dist/components/ui/dropdown-menu.js +40 -16
  125. package/dist/components/ui/dropdown-menu.mjs +3 -3
  126. package/dist/components/ui/empty.js +41 -5
  127. package/dist/components/ui/empty.mjs +2 -2
  128. package/dist/components/ui/expense-bar-chart.js +642 -0
  129. package/dist/components/ui/expense-bar-chart.mjs +16 -0
  130. package/dist/components/ui/field.js +53 -21
  131. package/dist/components/ui/field.mjs +4 -4
  132. package/dist/components/ui/financial-cards.js +1002 -0
  133. package/dist/components/ui/financial-cards.mjs +24 -0
  134. package/dist/components/ui/financial-drawers.js +637 -0
  135. package/dist/components/ui/financial-drawers.mjs +17 -0
  136. package/dist/components/ui/financial-primitives.js +218 -0
  137. package/dist/components/ui/financial-primitives.mjs +22 -0
  138. package/dist/components/ui/financial-sections.js +1422 -0
  139. package/dist/components/ui/financial-sections.mjs +30 -0
  140. package/dist/components/ui/form-primitives.js +682 -0
  141. package/dist/components/ui/form-primitives.mjs +19 -0
  142. package/dist/components/ui/income-bar-chart.js +641 -0
  143. package/dist/components/ui/income-bar-chart.mjs +16 -0
  144. package/dist/components/ui/input-group.js +43 -7
  145. package/dist/components/ui/input-group.mjs +5 -5
  146. package/dist/components/ui/input-otp.js +39 -3
  147. package/dist/components/ui/input-otp.mjs +2 -2
  148. package/dist/components/ui/input.js +34 -2
  149. package/dist/components/ui/input.mjs +2 -2
  150. package/dist/components/ui/kanban-column.js +1143 -0
  151. package/dist/components/ui/kanban-column.mjs +20 -0
  152. package/dist/components/ui/label.js +35 -7
  153. package/dist/components/ui/label.mjs +2 -2
  154. package/dist/components/ui/opportunity-card.js +960 -0
  155. package/dist/components/ui/opportunity-card.mjs +20 -0
  156. package/dist/components/ui/opportunity-edit-modals.js +3360 -0
  157. package/dist/components/ui/opportunity-edit-modals.mjs +37 -0
  158. package/dist/components/ui/opportunity-summary-tab.js +4365 -0
  159. package/dist/components/ui/opportunity-summary-tab.mjs +34 -0
  160. package/dist/components/ui/pagination.js +35 -3
  161. package/dist/components/ui/pagination.mjs +3 -3
  162. package/dist/components/ui/pipeline-alerts.js +103 -0
  163. package/dist/components/ui/pipeline-alerts.mjs +8 -0
  164. package/dist/components/ui/pipeline-board.js +1408 -0
  165. package/dist/components/ui/pipeline-board.mjs +24 -0
  166. package/dist/components/ui/pipeline-chart.js +216 -0
  167. package/dist/components/ui/pipeline-chart.mjs +10 -0
  168. package/dist/components/ui/pipeline-dialogs.js +1183 -0
  169. package/dist/components/ui/pipeline-dialogs.mjs +23 -0
  170. package/dist/components/ui/pipeline-primitives.js +300 -0
  171. package/dist/components/ui/pipeline-primitives.mjs +11 -0
  172. package/dist/components/ui/popover.js +45 -4
  173. package/dist/components/ui/popover.mjs +3 -3
  174. package/dist/components/ui/progress.js +33 -1
  175. package/dist/components/ui/progress.mjs +2 -2
  176. package/dist/components/ui/property-cashflow-doughnut-chart.js +523 -0
  177. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +16 -0
  178. package/dist/components/ui/property-debt-equity-doughnut-chart.js +521 -0
  179. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +16 -0
  180. package/dist/components/ui/property-mobile-estimate-line-chart.js +682 -0
  181. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +16 -0
  182. package/dist/components/ui/radio-group.js +33 -1
  183. package/dist/components/ui/radio-group.mjs +2 -2
  184. package/dist/components/ui/select.js +66 -26
  185. package/dist/components/ui/select.mjs +3 -3
  186. package/dist/components/ui/separator.js +33 -1
  187. package/dist/components/ui/separator.mjs +2 -2
  188. package/dist/components/ui/sheet.js +37 -9
  189. package/dist/components/ui/sheet.mjs +3 -3
  190. package/dist/components/ui/skeleton.js +33 -1
  191. package/dist/components/ui/skeleton.mjs +2 -2
  192. package/dist/components/ui/slider.js +86 -102
  193. package/dist/components/ui/slider.mjs +2 -2
  194. package/dist/components/ui/spinner.js +33 -1
  195. package/dist/components/ui/spinner.mjs +2 -2
  196. package/dist/components/ui/stage-timeline.js +579 -0
  197. package/dist/components/ui/stage-timeline.mjs +15 -0
  198. package/dist/components/ui/switch.js +37 -4
  199. package/dist/components/ui/switch.mjs +2 -3
  200. package/dist/components/ui/table.js +37 -5
  201. package/dist/components/ui/table.mjs +2 -2
  202. package/dist/components/ui/tabs.js +36 -12
  203. package/dist/components/ui/tabs.mjs +2 -2
  204. package/dist/components/ui/textarea.js +34 -2
  205. package/dist/components/ui/textarea.mjs +2 -2
  206. package/dist/components/ui/toggle-group.js +35 -4
  207. package/dist/components/ui/toggle-group.mjs +3 -4
  208. package/dist/components/ui/toggle.js +35 -4
  209. package/dist/components/ui/toggle.mjs +2 -3
  210. package/dist/components/ui/tooltip.js +51 -22
  211. package/dist/components/ui/tooltip.mjs +3 -3
  212. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +528 -0
  213. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +16 -0
  214. package/dist/components/ui/transactions-income-expense-bar-chart.js +516 -0
  215. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +16 -0
  216. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +528 -0
  217. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +16 -0
  218. package/dist/index.js +11613 -2868
  219. package/dist/index.mjs +377 -164
  220. package/dist/lib/theme-provider.js +10 -1
  221. package/dist/lib/theme-provider.mjs +1 -1
  222. package/dist/lib/typography.js +8 -0
  223. package/dist/lib/typography.mjs +3 -1
  224. package/dist/lib/utils.js +33 -1
  225. package/dist/lib/utils.mjs +1 -1
  226. package/dist/styles.css +1 -1
  227. package/package.json +169 -6
  228. package/src/components/index.tsx +323 -13
  229. package/src/components/ui/accordion.tsx +6 -3
  230. package/src/components/ui/add-column-modal.tsx +339 -0
  231. package/src/components/ui/add-lead-modal.tsx +290 -0
  232. package/src/components/ui/ai-assistant-drawer.tsx +408 -0
  233. package/src/components/ui/alert-dialog.tsx +80 -54
  234. package/src/components/ui/alert.tsx +28 -28
  235. package/src/components/ui/avatar.tsx +30 -29
  236. package/src/components/ui/backoffice-alert-history-chart.tsx +260 -0
  237. package/src/components/ui/backoffice-contact-history-chart.tsx +325 -0
  238. package/src/components/ui/badge.tsx +17 -15
  239. package/src/components/ui/borrowing-capacity-line-chart.tsx +357 -0
  240. package/src/components/ui/button.tsx +30 -27
  241. package/src/components/ui/calendar.tsx +53 -67
  242. package/src/components/ui/card.tsx +27 -24
  243. package/src/components/ui/cash-balance-line-chart.tsx +302 -0
  244. package/src/components/ui/cashflow-bar-chart.tsx +363 -0
  245. package/src/components/ui/chart-shared.tsx +261 -0
  246. package/src/components/ui/checkbox.tsx +30 -26
  247. package/src/components/ui/combobox.tsx +223 -0
  248. package/src/components/ui/data-table.tsx +160 -99
  249. package/src/components/ui/date-picker.tsx +0 -2
  250. package/src/components/ui/dialog.tsx +70 -60
  251. package/src/components/ui/drawer.tsx +57 -48
  252. package/src/components/ui/dropdown-menu.tsx +90 -82
  253. package/src/components/ui/empty.tsx +31 -27
  254. package/src/components/ui/expense-bar-chart.tsx +296 -0
  255. package/src/components/ui/field.tsx +70 -62
  256. package/src/components/ui/financial-cards.tsx +830 -0
  257. package/src/components/ui/financial-drawers.tsx +339 -0
  258. package/src/components/ui/financial-primitives.tsx +331 -0
  259. package/src/components/ui/financial-sections.tsx +672 -0
  260. package/src/components/ui/form-primitives.tsx +536 -0
  261. package/src/components/ui/income-bar-chart.tsx +297 -0
  262. package/src/components/ui/input-group.tsx +41 -34
  263. package/src/components/ui/input-otp.tsx +29 -24
  264. package/src/components/ui/input.tsx +8 -8
  265. package/src/components/ui/kanban-column.tsx +333 -0
  266. package/src/components/ui/label.tsx +9 -12
  267. package/src/components/ui/opportunity-card.tsx +616 -0
  268. package/src/components/ui/opportunity-edit-modals.tsx +2528 -0
  269. package/src/components/ui/opportunity-summary-tab.tsx +579 -0
  270. package/src/components/ui/pipeline-alerts.tsx +74 -0
  271. package/src/components/ui/pipeline-board.tsx +268 -0
  272. package/src/components/ui/pipeline-chart.tsx +173 -0
  273. package/src/components/ui/pipeline-dialogs.tsx +303 -0
  274. package/src/components/ui/pipeline-primitives.tsx +108 -0
  275. package/src/components/ui/popover.tsx +41 -36
  276. package/src/components/ui/property-cashflow-doughnut-chart.tsx +188 -0
  277. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +185 -0
  278. package/src/components/ui/property-mobile-estimate-line-chart.tsx +393 -0
  279. package/src/components/ui/select.tsx +65 -52
  280. package/src/components/ui/sheet.tsx +55 -52
  281. package/src/components/ui/slider.tsx +54 -77
  282. package/src/components/ui/stage-timeline.tsx +205 -0
  283. package/src/components/ui/switch.tsx +42 -29
  284. package/src/components/ui/table.tsx +28 -28
  285. package/src/components/ui/tabs.tsx +22 -28
  286. package/src/components/ui/textarea.tsx +8 -8
  287. package/src/components/ui/toggle-group.tsx +0 -2
  288. package/src/components/ui/toggle.tsx +13 -15
  289. package/src/components/ui/tooltip.tsx +30 -28
  290. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +191 -0
  291. package/src/components/ui/transactions-income-expense-bar-chart.tsx +205 -0
  292. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +191 -0
  293. package/src/lib/theme-provider.tsx +10 -0
  294. package/src/lib/typography.ts +9 -0
  295. package/src/lib/utils.ts +41 -3
  296. package/src/styles/globals.css +371 -124
  297. package/src/styles/styles-css.ts +1 -1
  298. package/tsup.config.ts +32 -0
  299. package/dist/chunk-K74JRTJR.mjs +0 -105
  300. package/dist/chunk-V7CNWJT3.mjs +0 -10
@@ -0,0 +1,339 @@
1
+ import * as React from "react";
2
+ import { X } from "lucide-react";
3
+ import { Sheet, SheetContent } from "./sheet";
4
+ import { Button } from "./button";
5
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs";
6
+ import { Spinner } from "./spinner";
7
+
8
+ /**
9
+ * Financial drawer shells — WealthX DS (Level 5)
10
+ *
11
+ * These are the outermost drawer containers. They compose the Level 4 sections
12
+ * into full-screen right-side panels used across backoffice.
13
+ *
14
+ * ```
15
+ * Level 2 → FinancialDetailField, FinancialLvrBar … (financial-primitives)
16
+ * Level 3 → PropertyCard, DebtCard, AlertCard … (financial-cards)
17
+ * Level 4 → PropertyHoldingsSection, DebtSection … (financial-sections)
18
+ * Level 5 → SummaryReportDrawer, OpportunityDetailsDrawer … ← here
19
+ * ```
20
+ *
21
+ * Both components are **visual shells** — they own the chrome (header, tabs,
22
+ * scrollable viewport) but delegate all data and content to the consuming app
23
+ * via slot props.
24
+ */
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // SummaryReportDrawer
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export type SummaryViewMode = "individual" | "joint";
31
+ export type SummaryJointSubTab = "joint" | "userA" | "userB";
32
+
33
+ export interface SummaryReportDrawerProps {
34
+ open: boolean;
35
+ onOpenChange: (open: boolean) => void;
36
+ /** Primary contact name shown in the header. */
37
+ contactName?: string;
38
+ /** Whether a secondary contact has been added for joint view. */
39
+ hasJointView?: boolean;
40
+ /** Active view mode — individual or joint. */
41
+ viewMode?: SummaryViewMode;
42
+ onViewModeChange?: (mode: SummaryViewMode) => void;
43
+ /** Active sub-tab when in joint + joint mode. */
44
+ jointSubTab?: SummaryJointSubTab;
45
+ onJointSubTabChange?: (tab: SummaryJointSubTab) => void;
46
+ /** Display name for the primary applicant in joint view. */
47
+ jointMainUserName?: string;
48
+ /** Display name for the secondary applicant in joint view. */
49
+ jointCoApplicantName?: string;
50
+ /** Called when "Create Joint View" is clicked (no joint view yet). */
51
+ onCreateJointView?: () => void;
52
+ /** Called when "Remove Joint View" is clicked. */
53
+ onRemoveJointView?: () => void;
54
+ /**
55
+ * Row of action buttons rendered below the header chrome.
56
+ * Typically: Export button, Send Report button, etc.
57
+ */
58
+ actionButtons?: React.ReactNode;
59
+ /**
60
+ * Alert accordion shown below the action buttons row.
61
+ * Pass an `<AlertAccordion>` from financial-sections when alerts exist.
62
+ */
63
+ alerts?: React.ReactNode;
64
+ /**
65
+ * When true, hides `children` and `actionButtons`/`alerts` and shows a
66
+ * centered `<Spinner>` instead.
67
+ */
68
+ isLoading?: boolean;
69
+ /**
70
+ * The scrollable financial content — stack Level 4 sections and charts here.
71
+ * Recommended order (mirrors backoffice): charts → PropertyHoldings → Debt →
72
+ * OtherLiabilities → IncomeExpense → AlertAccordion.
73
+ */
74
+ children?: React.ReactNode;
75
+ }
76
+
77
+ /**
78
+ * Right-side sheet shell for the Summary Report Drawer.
79
+ *
80
+ * Handles the header chrome (contact name, Individual/Joint view toggle,
81
+ * joint sub-tabs, close button) and a scrollable content viewport.
82
+ * All financial content is passed as `children`.
83
+ */
84
+ export function SummaryReportDrawer({
85
+ open,
86
+ onOpenChange,
87
+ contactName = "Contact",
88
+ hasJointView = false,
89
+ viewMode = "individual",
90
+ onViewModeChange,
91
+ jointSubTab = "joint",
92
+ onJointSubTabChange,
93
+ jointMainUserName = "User A",
94
+ jointCoApplicantName = "User B",
95
+ onCreateJointView,
96
+ onRemoveJointView,
97
+ actionButtons,
98
+ alerts,
99
+ isLoading = false,
100
+ children,
101
+ }: SummaryReportDrawerProps) {
102
+ return (
103
+ <Sheet open={open} onOpenChange={onOpenChange}>
104
+ <SheetContent
105
+ side="right"
106
+ showCloseButton={false}
107
+ className="w-[min(1080px,90vw)] max-w-none p-0"
108
+ >
109
+ <div className="flex max-h-[100dvh] min-h-0 flex-1 flex-col overflow-hidden px-8 py-6">
110
+ {/* ── Header chrome ── */}
111
+ <div className="shrink-0 space-y-4">
112
+ <div className="flex flex-col gap-3">
113
+ {/* Top row: view mode toggle + close */}
114
+ <div className="flex items-center justify-between">
115
+ <div className="flex items-center gap-2">
116
+ {hasJointView ? (
117
+ <>
118
+ <div className="flex gap-1">
119
+ <Button
120
+ variant={
121
+ viewMode === "individual" ? "default" : "outline"
122
+ }
123
+ size="sm"
124
+ onClick={() => onViewModeChange?.("individual")}
125
+ >
126
+ Individual View
127
+ </Button>
128
+ <Button
129
+ variant={viewMode === "joint" ? "default" : "outline"}
130
+ size="sm"
131
+ onClick={() => onViewModeChange?.("joint")}
132
+ >
133
+ Joint View
134
+ </Button>
135
+ </div>
136
+ <Button
137
+ variant="outline"
138
+ size="sm"
139
+ onClick={onRemoveJointView}
140
+ >
141
+ Remove Joint View
142
+ </Button>
143
+ </>
144
+ ) : (
145
+ <>
146
+ <span className="text-label-medium text-foreground">
147
+ {contactName} View
148
+ </span>
149
+ <Button
150
+ variant="outline"
151
+ size="sm"
152
+ onClick={onCreateJointView}
153
+ >
154
+ Create Joint View
155
+ </Button>
156
+ </>
157
+ )}
158
+ </div>
159
+
160
+ <Button
161
+ variant="ghost"
162
+ size="icon-sm"
163
+ onClick={() => onOpenChange(false)}
164
+ aria-label="Close drawer"
165
+ >
166
+ <X className="h-4 w-4" />
167
+ </Button>
168
+ </div>
169
+
170
+ {/* Joint sub-tabs — default (pill) variant, only in joint+joint mode */}
171
+ {hasJointView && viewMode === "joint" && (
172
+ <Tabs
173
+ value={jointSubTab}
174
+ onValueChange={(v) =>
175
+ onJointSubTabChange?.(v as SummaryJointSubTab)
176
+ }
177
+ >
178
+ <TabsList>
179
+ <TabsTrigger value="joint">Joint View</TabsTrigger>
180
+ <TabsTrigger value="userA">
181
+ {jointMainUserName} View
182
+ </TabsTrigger>
183
+ <TabsTrigger value="userB">
184
+ {jointCoApplicantName} View
185
+ </TabsTrigger>
186
+ </TabsList>
187
+ </Tabs>
188
+ )}
189
+ </div>
190
+
191
+ {/* Action buttons + alerts — hidden while loading */}
192
+ {!isLoading && actionButtons && <div>{actionButtons}</div>}
193
+ {!isLoading && alerts && <div>{alerts}</div>}
194
+ </div>
195
+
196
+ {/* ── Scrollable content viewport ── */}
197
+ <div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
198
+ {isLoading ? (
199
+ <div className="flex h-full min-h-[200px] items-center justify-center py-12">
200
+ <Spinner size="xl" className="text-primary" />
201
+ </div>
202
+ ) : (
203
+ children
204
+ )}
205
+ </div>
206
+ </div>
207
+ </SheetContent>
208
+ </Sheet>
209
+ );
210
+ }
211
+
212
+ // ---------------------------------------------------------------------------
213
+ // OpportunityDetailsDrawer
214
+ // ---------------------------------------------------------------------------
215
+
216
+ export type OpportunityTab =
217
+ | "summary"
218
+ | "tasks"
219
+ | "bankStatement"
220
+ | "policyAI"
221
+ | "runServicing"
222
+ | "emailAndNotes"
223
+ | "syncLoanApp";
224
+
225
+ const OPPORTUNITY_TABS: { value: OpportunityTab; label: string }[] = [
226
+ { value: "summary", label: "Summary" },
227
+ { value: "tasks", label: "Tasks" },
228
+ { value: "bankStatement", label: "Reports & Statements" },
229
+ { value: "policyAI", label: "Policy AI" },
230
+ { value: "runServicing", label: "Run Servicing" },
231
+ { value: "emailAndNotes", label: "Email & Notes" },
232
+ { value: "syncLoanApp", label: "Sync Loan App" },
233
+ ];
234
+
235
+ export interface OpportunityDetailsDrawerProps {
236
+ open: boolean;
237
+ onOpenChange: (open: boolean) => void;
238
+ /** Default active tab on open. Defaults to "summary". */
239
+ defaultTab?: OpportunityTab;
240
+ /**
241
+ * Content slots keyed by tab value.
242
+ * Pass only the tabs you want to render content for — unset tabs show nothing.
243
+ *
244
+ * ```tsx
245
+ * <OpportunityDetailsDrawer
246
+ * tabs={{
247
+ * summary: <SummaryTabContent />,
248
+ * tasks: <TasksTabContent />,
249
+ * }}
250
+ * />
251
+ * ```
252
+ */
253
+ tabs?: Partial<Record<OpportunityTab, React.ReactNode>>;
254
+ }
255
+
256
+ /**
257
+ * Right-side sheet shell for the Opportunity Details Drawer.
258
+ *
259
+ * Renders a line-variant tab bar (Summary · Tasks · Reports & Statements ·
260
+ * Policy AI · Run Servicing · Email & Notes · Sync Loan App) with a close
261
+ * button, and a scrollable `TabsContent` panel for each active tab.
262
+ *
263
+ * Uses the shadcn `Tabs` component internally so each tab's content panel
264
+ * is properly connected to its trigger via Base UI's Tabs.Root context.
265
+ */
266
+ export function OpportunityDetailsDrawer({
267
+ open,
268
+ onOpenChange,
269
+ defaultTab = "summary",
270
+ tabs,
271
+ }: OpportunityDetailsDrawerProps) {
272
+ return (
273
+ <Sheet open={open} onOpenChange={onOpenChange}>
274
+ <SheetContent
275
+ side="right"
276
+ showCloseButton={false}
277
+ className="w-[min(1048px,100vw)] max-w-none p-0"
278
+ >
279
+ {/*
280
+ * Tabs.Root wraps both the header and content so that TabsContent panels
281
+ * are in scope and automatically shown/hidden by Base UI's tab context.
282
+ * gap-0 overrides the default gap-2 on Tabs.
283
+ */}
284
+ <Tabs defaultValue={defaultTab} className="h-full gap-0">
285
+ {/*
286
+ * Tab header: no pt-3 so the header height hugs the tab section.
287
+ * TabsList gets group-data-[orientation=horizontal]/tabs:h-12 to
288
+ * override the built-in conditional h-9 at equal specificity via
289
+ * cascade order. Triggers use flex-none to hug their label width
290
+ * (overrides the built-in flex-1 that was stretching them equally).
291
+ * No overflow-x-auto wrapper — that forces overflow-y:auto (CSS spec)
292
+ * and clips the line-variant after:bottom-[-5px] indicator.
293
+ */}
294
+ <div className="flex shrink-0 items-end border-b border-border">
295
+ <TabsList
296
+ variant="line"
297
+ className="flex-1 group-data-[orientation=horizontal]/tabs:h-auto justify-start rounded-none bg-transparent p-0"
298
+ >
299
+ {OPPORTUNITY_TABS.map((tab) => (
300
+ <TabsTrigger
301
+ key={tab.value}
302
+ value={tab.value}
303
+ className="flex-none h-auto py-3 px-4 text-sm"
304
+ >
305
+ {tab.label}
306
+ </TabsTrigger>
307
+ ))}
308
+ </TabsList>
309
+
310
+ {/* Close button */}
311
+ <div className="flex shrink-0 self-center items-center pr-3">
312
+ <Button
313
+ variant="ghost"
314
+ size="icon-sm"
315
+ onClick={() => onOpenChange(false)}
316
+ aria-label="Close drawer"
317
+ >
318
+ <X className="h-4 w-4" />
319
+ </Button>
320
+ </div>
321
+ </div>
322
+
323
+ {/* ── Tab content panels ── */}
324
+ <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
325
+ {OPPORTUNITY_TABS.map((tab) => (
326
+ <TabsContent
327
+ key={tab.value}
328
+ value={tab.value}
329
+ className="mt-0 flex-none p-5 outline-none"
330
+ >
331
+ {tabs?.[tab.value]}
332
+ </TabsContent>
333
+ ))}
334
+ </div>
335
+ </Tabs>
336
+ </SheetContent>
337
+ </Sheet>
338
+ );
339
+ }
@@ -0,0 +1,331 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ /**
5
+ * Financial display primitives — WealthX DS
6
+ *
7
+ * Atomic building blocks for financial data display.
8
+ * Used inside summary report drawers, opportunity detail panels,
9
+ * and any financial data card in the backoffice and frontend.
10
+ *
11
+ * Compose these upward:
12
+ * Atom → FinancialDetailField, FinancialLineItem, FinancialLvrBar …
13
+ * Card → PropertyCard, DebtCard, ApplicantInfoCard …
14
+ * Section → PropertyHoldingsGrid, IncomeExpensesGrid …
15
+ * Drawer → SummaryReportDrawer, OpportunityDetailsDrawer …
16
+ */
17
+
18
+ const NO_DATA = "—";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // FinancialSectionLabel
22
+ // ---------------------------------------------------------------------------
23
+
24
+ export interface FinancialSectionLabelProps {
25
+ children: React.ReactNode;
26
+ }
27
+
28
+ /** All-caps muted section heading. Used above major sections inside a financial drawer. */
29
+ export function FinancialSectionLabel({
30
+ children,
31
+ }: FinancialSectionLabelProps) {
32
+ return (
33
+ <span className="text-label-small uppercase text-muted-foreground">
34
+ {children}
35
+ </span>
36
+ );
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // FinancialCardHeader
41
+ // ---------------------------------------------------------------------------
42
+
43
+ export interface FinancialCardHeaderProps {
44
+ children: React.ReactNode;
45
+ }
46
+
47
+ /** All-caps muted label at the top of a card — lighter than SectionLabel. */
48
+ export function FinancialCardHeader({ children }: FinancialCardHeaderProps) {
49
+ return (
50
+ <div className="py-2.5">
51
+ <span className="text-label-small uppercase text-muted-foreground">
52
+ {children}
53
+ </span>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // FinancialSubsectionTitle
60
+ // ---------------------------------------------------------------------------
61
+
62
+ export interface FinancialSubsectionTitleProps {
63
+ children: React.ReactNode;
64
+ }
65
+
66
+ /** Uppercase bold title inside a card section (e.g. "Card Stats", "Loan Stats"). */
67
+ export function FinancialSubsectionTitle({
68
+ children,
69
+ }: FinancialSubsectionTitleProps) {
70
+ return (
71
+ <span className="text-label-medium uppercase text-foreground">
72
+ {children}
73
+ </span>
74
+ );
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // FinancialDetailField
79
+ // ---------------------------------------------------------------------------
80
+
81
+ export type FinancialDetailFieldVariant = "caption" | "overline" | "footer";
82
+
83
+ export interface FinancialDetailFieldProps {
84
+ label: string;
85
+ /**
86
+ * The value to display. Accepts any renderable node — formatted strings,
87
+ * badges, or `null`/`undefined` (renders `—`).
88
+ */
89
+ value?: React.ReactNode;
90
+ /**
91
+ * Controls label typography and layout density:
92
+ * - `caption` (default) — small 2-line min-height label; use in dense grids
93
+ * - `overline` — tighter gap; use in mid-density sections
94
+ * - `footer` — flex-col layout; use inside `FinancialSubtotalFrame`
95
+ */
96
+ variant?: FinancialDetailFieldVariant;
97
+ }
98
+
99
+ /**
100
+ * Label + value vertical stack — the core data-display cell.
101
+ *
102
+ * | Variant | Label style | Use when |
103
+ * |-----------|------------------------|-----------------------------------|
104
+ * | caption | small, 2-line min-h | Dense grids (default) |
105
+ * | overline | overline, tighter gap | Mid-density sections |
106
+ * | footer | overline + flex-col | SubtotalFrame cells |
107
+ */
108
+ export function FinancialDetailField({
109
+ label,
110
+ value,
111
+ variant = "caption",
112
+ }: FinancialDetailFieldProps) {
113
+ if (variant === "footer") {
114
+ return (
115
+ <div className="flex flex-col justify-between min-w-0">
116
+ <span className="text-overline text-muted-foreground">{label}</span>
117
+ <span className="text-label-medium text-foreground mt-1 break-words">
118
+ {value ?? NO_DATA}
119
+ </span>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ const labelClass =
125
+ variant === "caption"
126
+ ? "block min-h-[2rem] text-caption text-muted-foreground"
127
+ : "min-h-[1.75rem] text-overline text-muted-foreground";
128
+
129
+ return (
130
+ <div
131
+ className={cn(
132
+ "min-w-0",
133
+ variant === "overline" && "flex flex-col gap-0.5",
134
+ )}
135
+ >
136
+ <span className={labelClass}>{label}</span>
137
+ <span className="text-label-medium text-foreground break-words">
138
+ {value ?? NO_DATA}
139
+ </span>
140
+ </div>
141
+ );
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // FinancialLineItem
146
+ // ---------------------------------------------------------------------------
147
+
148
+ export interface FinancialLineItemProps {
149
+ label: string;
150
+ /** Formatted value string e.g. "$9,500". Renders `—` when falsy. */
151
+ value?: string | null;
152
+ /**
153
+ * When `true`, renders the value in `text-destructive` (red).
154
+ * Use for expenses, liabilities, or negative cashflow values.
155
+ */
156
+ destructive?: boolean;
157
+ }
158
+
159
+ /**
160
+ * Horizontal row: label on left, value on right.
161
+ * Use `destructive` for expenses, liabilities, or negative cashflow values.
162
+ */
163
+ export function FinancialLineItem({
164
+ label,
165
+ value,
166
+ destructive,
167
+ }: FinancialLineItemProps) {
168
+ return (
169
+ <div className="flex items-center justify-between gap-4 py-0.5">
170
+ <span className="text-body-small text-foreground">{label}</span>
171
+ <span
172
+ className={cn(
173
+ "text-label-medium whitespace-nowrap text-right",
174
+ destructive ? "text-destructive" : "text-foreground",
175
+ )}
176
+ >
177
+ {value || NO_DATA}
178
+ </span>
179
+ </div>
180
+ );
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // FinancialLvrBar
185
+ // ---------------------------------------------------------------------------
186
+
187
+ export interface FinancialLvrBarProps {
188
+ /**
189
+ * LVR percentage (0–100). Drives colour threshold:
190
+ * - `< 70` → success (low risk)
191
+ * - `70–79` → warning (borderline)
192
+ * - `≥ 80` → destructive (high risk)
193
+ */
194
+ percent?: number;
195
+ /** Human-readable label rendered on the right e.g. `"56% — Good"`. */
196
+ label?: string;
197
+ }
198
+
199
+ /**
200
+ * LVR progress bar with label.
201
+ *
202
+ * Colour thresholds map to semantic tokens:
203
+ * - < 70% → success (low risk)
204
+ * - 70–79% → warning (borderline)
205
+ * - ≥ 80% → destructive (high risk)
206
+ */
207
+ export function FinancialLvrBar({
208
+ percent = 56,
209
+ label = "56% — Good",
210
+ }: FinancialLvrBarProps) {
211
+ const color =
212
+ percent >= 80 ? "destructive" : percent >= 70 ? "warning" : "success";
213
+
214
+ const textClass = {
215
+ success: "text-success",
216
+ warning: "text-warning",
217
+ destructive: "text-destructive",
218
+ }[color];
219
+
220
+ const trackClass = {
221
+ success: "bg-success/15",
222
+ warning: "bg-warning/15",
223
+ destructive: "bg-destructive/15",
224
+ }[color];
225
+
226
+ const fillClass = {
227
+ success: "bg-success",
228
+ warning: "bg-warning",
229
+ destructive: "bg-destructive",
230
+ }[color];
231
+
232
+ return (
233
+ <div className="flex flex-col gap-2">
234
+ <div className="flex items-center justify-between">
235
+ <span className="text-body-small text-muted-foreground">LVR</span>
236
+ <span className={cn("text-label-medium", textClass)}>{label}</span>
237
+ </div>
238
+ <div className={cn("h-2.5 w-full overflow-hidden", trackClass)}>
239
+ <div
240
+ className={cn("h-full", fillClass)}
241
+ style={{ width: `${Math.min(percent, 100)}%` }}
242
+ />
243
+ </div>
244
+ </div>
245
+ );
246
+ }
247
+
248
+ // ---------------------------------------------------------------------------
249
+ // FinancialSubtotalFrame
250
+ // ---------------------------------------------------------------------------
251
+
252
+ export interface FinancialSubtotalFrameProps {
253
+ children: React.ReactNode;
254
+ }
255
+
256
+ /**
257
+ * Brand-tinted footer container — sits at the bottom of a property or debt card.
258
+ * Background uses the tenant primary color at 10 % opacity.
259
+ */
260
+ export function FinancialSubtotalFrame({
261
+ children,
262
+ }: FinancialSubtotalFrameProps) {
263
+ return (
264
+ <div className="mt-auto border-t border-primary/20 bg-primary/10 px-4 py-3">
265
+ {children}
266
+ </div>
267
+ );
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // FinancialSubtotalBlock
272
+ // ---------------------------------------------------------------------------
273
+
274
+ export interface FinancialSubtotalBlockProps {
275
+ /**
276
+ * Monthly average value e.g. `"$2,840"`.
277
+ * When provided without `totalLast12Months`, renders right-aligned as a single figure.
278
+ */
279
+ monthlyAverage?: string;
280
+ /** Total for the last 12 months e.g. `"$34,080"`. Only shown when provided alongside `monthlyAverage`. */
281
+ totalLast12Months?: string;
282
+ /** Override the "Monthly Average" label (e.g. `"Net Surplus"`). */
283
+ label?: string;
284
+ }
285
+
286
+ /**
287
+ * One or two summary values inside a SubtotalFrame.
288
+ *
289
+ * - Two values → Monthly Average (left) + Total Last 12 Months (right)
290
+ * - One value → right-aligned single figure (pass only `monthlyAverage`)
291
+ * - Custom label → override "Monthly Average" via `label` prop
292
+ */
293
+ export function FinancialSubtotalBlock({
294
+ monthlyAverage,
295
+ totalLast12Months,
296
+ label,
297
+ }: FinancialSubtotalBlockProps) {
298
+ const isSingle = monthlyAverage && !totalLast12Months;
299
+ return (
300
+ <div
301
+ className={cn(
302
+ "flex flex-row items-center",
303
+ isSingle ? "justify-end" : "justify-between",
304
+ )}
305
+ >
306
+ <div
307
+ className={cn(
308
+ "flex flex-col gap-0.5",
309
+ isSingle ? "text-right" : "text-left",
310
+ )}
311
+ >
312
+ <span className="min-h-[1.75rem] text-overline text-muted-foreground">
313
+ {label ?? "Monthly Average"}
314
+ </span>
315
+ <span className="text-label-medium text-foreground">
316
+ {monthlyAverage ?? NO_DATA}
317
+ </span>
318
+ </div>
319
+ {!isSingle && (
320
+ <div className="flex flex-col gap-0.5 text-right">
321
+ <span className="min-h-[1.75rem] text-overline text-muted-foreground">
322
+ Total Last 12 Months
323
+ </span>
324
+ <span className="text-label-medium text-foreground">
325
+ {totalLast12Months ?? NO_DATA}
326
+ </span>
327
+ </div>
328
+ )}
329
+ </div>
330
+ );
331
+ }