@wealthx/shadcn 1.2.1 → 1.3.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 (247) hide show
  1. package/.turbo/turbo-build.log +203 -150
  2. package/CHANGELOG.md +29 -0
  3. package/dist/{chunk-4Y6R4WEC.mjs → chunk-2A5RRQGG.mjs} +9 -22
  4. package/dist/{chunk-TS2ZX2VS.mjs → chunk-2UM72RJ7.mjs} +11 -15
  5. package/dist/{chunk-A56YQQHG.mjs → chunk-3NCUZIFP.mjs} +2 -2
  6. package/dist/chunk-3OYFOX3X.mjs +79 -0
  7. package/dist/{chunk-RP3SQYA3.mjs → chunk-3TTACBDP.mjs} +9 -4
  8. package/dist/chunk-4GAWMKMI.mjs +710 -0
  9. package/dist/{chunk-SYOD63OZ.mjs → chunk-5FQIKDKP.mjs} +6 -6
  10. package/dist/{chunk-K3JYD4IU.mjs → chunk-5IS7G74I.mjs} +11 -4
  11. package/dist/chunk-6AW4KJHE.mjs +235 -0
  12. package/dist/chunk-6CR5N2JW.mjs +302 -0
  13. package/dist/{chunk-XIRTEFKH.mjs → chunk-6DZEXFNB.mjs} +36 -8
  14. package/dist/chunk-6O6KD7CE.mjs +271 -0
  15. package/dist/chunk-7PV3IWCN.mjs +33 -0
  16. package/dist/{chunk-SPJ5KXW7.mjs → chunk-7S5AESZO.mjs} +5 -5
  17. package/dist/{chunk-RYCLWMZ7.mjs → chunk-ABFDMHOR.mjs} +9 -7
  18. package/dist/{chunk-SWGT756Z.mjs → chunk-AMQZRHEZ.mjs} +10 -4
  19. package/dist/{chunk-WOEHFRGB.mjs → chunk-BDYZCBRT.mjs} +4 -4
  20. package/dist/{chunk-WAZD7NFU.mjs → chunk-BKNFWEH2.mjs} +6 -6
  21. package/dist/{chunk-CLIN5525.mjs → chunk-C7CQJNMR.mjs} +1 -1
  22. package/dist/{chunk-D4ILTPOG.mjs → chunk-CFMQP5QS.mjs} +5 -4
  23. package/dist/{chunk-VPBN3WOO.mjs → chunk-DGHAXJBN.mjs} +9 -7
  24. package/dist/chunk-DOEO3CDL.mjs +27 -0
  25. package/dist/{chunk-KUDCQ4FI.mjs → chunk-DUJTAXMH.mjs} +9 -4
  26. package/dist/{chunk-GGM2UYGG.mjs → chunk-EBXQWIYG.mjs} +10 -4
  27. package/dist/{chunk-PMB3A7V3.mjs → chunk-EI5F6FMT.mjs} +1 -1
  28. package/dist/chunk-EWRB4PAD.mjs +468 -0
  29. package/dist/chunk-FAKPBKLT.mjs +253 -0
  30. package/dist/chunk-FNQXOAYJ.mjs +169 -0
  31. package/dist/{chunk-4CX4SBRO.mjs → chunk-GHC7LLUX.mjs} +14 -5
  32. package/dist/chunk-HBZLGDIN.mjs +507 -0
  33. package/dist/{chunk-SIZMLSRU.mjs → chunk-HISNT2MG.mjs} +8 -6
  34. package/dist/{chunk-PR6V5XKM.mjs → chunk-HVY6KCCF.mjs} +7 -4
  35. package/dist/chunk-I3RZS7V2.mjs +136 -0
  36. package/dist/chunk-IAE3F7DR.mjs +1962 -0
  37. package/dist/{chunk-ZRO5JO3H.mjs → chunk-IHMFS7NZ.mjs} +81 -84
  38. package/dist/{chunk-PCPLO5HT.mjs → chunk-IOJRDS6V.mjs} +96 -14
  39. package/dist/{chunk-LHYCMLVA.mjs → chunk-JKGDCQTZ.mjs} +11 -4
  40. package/dist/{chunk-H45TKD34.mjs → chunk-JMHR3YGZ.mjs} +1 -1
  41. package/dist/{chunk-4MN6UQHG.mjs → chunk-K5A5L6T2.mjs} +17 -39
  42. package/dist/{chunk-CSDO6VBW.mjs → chunk-LBMRIB3G.mjs} +10 -10
  43. package/dist/chunk-LV35NGVG.mjs +272 -0
  44. package/dist/{chunk-FZIXGLMV.mjs → chunk-M3FV7LOK.mjs} +5 -12
  45. package/dist/{chunk-FMAXJ2SI.mjs → chunk-MBON7YRJ.mjs} +1 -1
  46. package/dist/chunk-MIZQHHUO.mjs +441 -0
  47. package/dist/chunk-MN5NYQCL.mjs +29 -0
  48. package/dist/chunk-NL3ZO62D.mjs +31 -0
  49. package/dist/{chunk-Q76O3RIQ.mjs → chunk-NMOI6CQD.mjs} +1 -1
  50. package/dist/{chunk-P6AM5V7O.mjs → chunk-OODBHKG7.mjs} +1 -1
  51. package/dist/chunk-PBL4OQV2.mjs +283 -0
  52. package/dist/{chunk-3WMX6KWS.mjs → chunk-PU4YZQXV.mjs} +11 -12
  53. package/dist/chunk-QMY3AZJH.mjs +80 -0
  54. package/dist/{chunk-BL3DXM2X.mjs → chunk-QZ4RE6NA.mjs} +11 -4
  55. package/dist/{chunk-VACKZOMY.mjs → chunk-R3VSPKNP.mjs} +3 -3
  56. package/dist/{chunk-OPNQAVVH.mjs → chunk-RJI6GKVF.mjs} +8 -6
  57. package/dist/{chunk-WG6JGJXB.mjs → chunk-T4BJLT57.mjs} +1 -1
  58. package/dist/chunk-U4NDAF2P.mjs +207 -0
  59. package/dist/{chunk-DOH3EHX7.mjs → chunk-U5X52X37.mjs} +1 -1
  60. package/dist/chunk-UMTOX62O.mjs +415 -0
  61. package/dist/{chunk-7MMXNK3C.mjs → chunk-VLARHE5V.mjs} +8 -6
  62. package/dist/{chunk-2I5S2AMY.mjs → chunk-XREGSKX3.mjs} +2 -2
  63. package/dist/{chunk-JNQORUPP.mjs → chunk-YJG55G2H.mjs} +14 -11
  64. package/dist/chunk-ZC45IGZO.mjs +388 -0
  65. package/dist/components/ui/add-column-modal.js +42 -14
  66. package/dist/components/ui/add-column-modal.mjs +4 -4
  67. package/dist/components/ui/add-lead-modal.js +42 -11
  68. package/dist/components/ui/add-lead-modal.mjs +3 -3
  69. package/dist/components/ui/advisor-card.js +497 -0
  70. package/dist/components/ui/advisor-card.mjs +13 -0
  71. package/dist/components/ui/ai-assistant-drawer.js +11 -10
  72. package/dist/components/ui/ai-assistant-drawer.mjs +3 -3
  73. package/dist/components/ui/alert-dialog.js +2 -2
  74. package/dist/components/ui/alert-dialog.mjs +2 -2
  75. package/dist/components/ui/appointment-action-dialogs.js +1160 -0
  76. package/dist/components/ui/appointment-action-dialogs.mjs +23 -0
  77. package/dist/components/ui/appointment-availability-settings.js +1590 -0
  78. package/dist/components/ui/appointment-availability-settings.mjs +23 -0
  79. package/dist/components/ui/appointment-book-dialog.js +1744 -0
  80. package/dist/components/ui/appointment-book-dialog.mjs +27 -0
  81. package/dist/components/ui/appointment-calendar-view.js +833 -0
  82. package/dist/components/ui/appointment-calendar-view.mjs +14 -0
  83. package/dist/components/ui/appointment-detail-sheet.js +1517 -0
  84. package/dist/components/ui/appointment-detail-sheet.mjs +24 -0
  85. package/dist/components/ui/appointment-gmail-connect.js +467 -0
  86. package/dist/components/ui/appointment-gmail-connect.mjs +14 -0
  87. package/dist/components/ui/appointment-mini-card.js +345 -0
  88. package/dist/components/ui/appointment-mini-card.mjs +11 -0
  89. package/dist/components/ui/appointment-time-slot-picker.js +311 -0
  90. package/dist/components/ui/appointment-time-slot-picker.mjs +13 -0
  91. package/dist/components/ui/appointment-upcoming-card.js +1268 -0
  92. package/dist/components/ui/appointment-upcoming-card.mjs +21 -0
  93. package/dist/components/ui/backoffice-alert-history-chart.js +11 -5
  94. package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -4
  95. package/dist/components/ui/backoffice-alerts-chart.js +786 -0
  96. package/dist/components/ui/backoffice-alerts-chart.mjs +19 -0
  97. package/dist/components/ui/backoffice-connections-chart.js +817 -0
  98. package/dist/components/ui/backoffice-connections-chart.mjs +19 -0
  99. package/dist/components/ui/backoffice-contact-history-chart.js +11 -5
  100. package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -4
  101. package/dist/components/ui/badge.js +6 -6
  102. package/dist/components/ui/badge.mjs +1 -1
  103. package/dist/components/ui/borrowing-capacity-line-chart.js +30 -21
  104. package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -4
  105. package/dist/components/ui/button.js +2 -2
  106. package/dist/components/ui/button.mjs +1 -1
  107. package/dist/components/ui/calendar.js +2 -2
  108. package/dist/components/ui/calendar.mjs +2 -2
  109. package/dist/components/ui/card.js +1 -1
  110. package/dist/components/ui/card.mjs +1 -1
  111. package/dist/components/ui/cash-balance-line-chart.js +31 -23
  112. package/dist/components/ui/cash-balance-line-chart.mjs +5 -4
  113. package/dist/components/ui/cashflow-bar-chart.js +12 -5
  114. package/dist/components/ui/cashflow-bar-chart.mjs +5 -4
  115. package/dist/components/ui/chip.js +97 -18
  116. package/dist/components/ui/chip.mjs +3 -2
  117. package/dist/components/ui/color-picker.js +547 -0
  118. package/dist/components/ui/color-picker.mjs +24 -0
  119. package/dist/components/ui/data-table.js +182 -129
  120. package/dist/components/ui/data-table.mjs +3 -2
  121. package/dist/components/ui/date-picker.js +48 -27
  122. package/dist/components/ui/date-picker.mjs +4 -3
  123. package/dist/components/ui/dialog.js +37 -9
  124. package/dist/components/ui/dialog.mjs +2 -2
  125. package/dist/components/ui/expense-bar-chart.js +12 -5
  126. package/dist/components/ui/expense-bar-chart.mjs +5 -4
  127. package/dist/components/ui/field.mjs +2 -2
  128. package/dist/components/ui/financial-cards.js +322 -155
  129. package/dist/components/ui/financial-cards.mjs +5 -3
  130. package/dist/components/ui/financial-drawers.js +2 -2
  131. package/dist/components/ui/financial-drawers.mjs +3 -3
  132. package/dist/components/ui/financial-sections.js +14 -10
  133. package/dist/components/ui/financial-sections.mjs +6 -5
  134. package/dist/components/ui/form-primitives.js +4 -4
  135. package/dist/components/ui/form-primitives.mjs +3 -3
  136. package/dist/components/ui/income-bar-chart.js +12 -5
  137. package/dist/components/ui/income-bar-chart.mjs +5 -4
  138. package/dist/components/ui/input-group.js +2 -2
  139. package/dist/components/ui/input-group.mjs +2 -2
  140. package/dist/components/ui/kanban-column.js +52 -44
  141. package/dist/components/ui/kanban-column.mjs +7 -5
  142. package/dist/components/ui/opportunity-card.js +52 -44
  143. package/dist/components/ui/opportunity-card.mjs +6 -4
  144. package/dist/components/ui/opportunity-edit-modals.js +1371 -1267
  145. package/dist/components/ui/opportunity-edit-modals.mjs +10 -10
  146. package/dist/components/ui/opportunity-summary-tab.js +2748 -2161
  147. package/dist/components/ui/opportunity-summary-tab.mjs +16 -16
  148. package/dist/components/ui/page-header.js +92 -0
  149. package/dist/components/ui/page-header.mjs +8 -0
  150. package/dist/components/ui/page-top-bar.js +88 -0
  151. package/dist/components/ui/page-top-bar.mjs +8 -0
  152. package/dist/components/ui/pagination.js +303 -19
  153. package/dist/components/ui/pagination.mjs +11 -4
  154. package/dist/components/ui/pipeline-board.js +209 -195
  155. package/dist/components/ui/pipeline-board.mjs +10 -8
  156. package/dist/components/ui/pipeline-dialogs.js +118 -69
  157. package/dist/components/ui/pipeline-dialogs.mjs +8 -7
  158. package/dist/components/ui/pipeline-primitives.js +6 -6
  159. package/dist/components/ui/pipeline-primitives.mjs +2 -2
  160. package/dist/components/ui/property-cashflow-doughnut-chart.js +14 -12
  161. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -4
  162. package/dist/components/ui/property-debt-equity-doughnut-chart.js +14 -12
  163. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -4
  164. package/dist/components/ui/property-mobile-estimate-line-chart.js +16 -14
  165. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -4
  166. package/dist/components/ui/sidebar-nav.js +679 -0
  167. package/dist/components/ui/sidebar-nav.mjs +14 -0
  168. package/dist/components/ui/stage-timeline.js +6 -6
  169. package/dist/components/ui/stage-timeline.mjs +3 -3
  170. package/dist/components/ui/stepper.js +283 -0
  171. package/dist/components/ui/stepper.mjs +18 -0
  172. package/dist/components/ui/toggle-group.js +4 -4
  173. package/dist/components/ui/toggle-group.mjs +2 -2
  174. package/dist/components/ui/toggle.js +4 -4
  175. package/dist/components/ui/toggle.mjs +1 -1
  176. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +18 -16
  177. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -4
  178. package/dist/components/ui/transactions-income-expense-bar-chart.js +28 -12
  179. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -4
  180. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +18 -16
  181. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -4
  182. package/dist/index.js +12927 -8522
  183. package/dist/index.mjs +288 -190
  184. package/dist/lib/typography.js +10 -10
  185. package/dist/lib/typography.mjs +1 -1
  186. package/dist/styles.css +1 -1
  187. package/package.json +86 -1
  188. package/src/components/index.tsx +146 -0
  189. package/src/components/ui/add-column-modal.tsx +7 -7
  190. package/src/components/ui/add-lead-modal.tsx +6 -3
  191. package/src/components/ui/advisor-card.tsx +227 -0
  192. package/src/components/ui/ai-assistant-drawer.tsx +4 -3
  193. package/src/components/ui/appointment-action-dialogs.tsx +297 -0
  194. package/src/components/ui/appointment-availability-settings.tsx +645 -0
  195. package/src/components/ui/appointment-book-dialog.tsx +618 -0
  196. package/src/components/ui/appointment-calendar-view.tsx +510 -0
  197. package/src/components/ui/appointment-detail-sheet.tsx +415 -0
  198. package/src/components/ui/appointment-gmail-connect.tsx +188 -0
  199. package/src/components/ui/appointment-mini-card.tsx +104 -0
  200. package/src/components/ui/appointment-time-slot-picker.tsx +123 -0
  201. package/src/components/ui/appointment-upcoming-card.tsx +635 -0
  202. package/src/components/ui/backoffice-alert-history-chart.tsx +10 -2
  203. package/src/components/ui/backoffice-alerts-chart.tsx +312 -0
  204. package/src/components/ui/backoffice-connections-chart.tsx +339 -0
  205. package/src/components/ui/backoffice-contact-history-chart.tsx +10 -2
  206. package/src/components/ui/badge.tsx +12 -6
  207. package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -11
  208. package/src/components/ui/button.tsx +2 -2
  209. package/src/components/ui/card.tsx +1 -1
  210. package/src/components/ui/cash-balance-line-chart.tsx +4 -23
  211. package/src/components/ui/cashflow-bar-chart.tsx +9 -2
  212. package/src/components/ui/chart-shared.tsx +4 -11
  213. package/src/components/ui/chip.tsx +23 -19
  214. package/src/components/ui/color-picker.tsx +309 -0
  215. package/src/components/ui/data-table.tsx +117 -83
  216. package/src/components/ui/date-picker.tsx +42 -37
  217. package/src/components/ui/dialog.tsx +72 -6
  218. package/src/components/ui/expense-bar-chart.tsx +11 -2
  219. package/src/components/ui/financial-cards.tsx +99 -10
  220. package/src/components/ui/income-bar-chart.tsx +11 -2
  221. package/src/components/ui/opportunity-card.tsx +10 -39
  222. package/src/components/ui/opportunity-edit-modals.tsx +98 -36
  223. package/src/components/ui/opportunity-summary-tab.tsx +548 -232
  224. package/src/components/ui/page-header.tsx +57 -0
  225. package/src/components/ui/page-top-bar.tsx +48 -0
  226. package/src/components/ui/pagination.tsx +171 -22
  227. package/src/components/ui/pipeline-board.tsx +12 -5
  228. package/src/components/ui/property-cashflow-doughnut-chart.tsx +3 -1
  229. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +3 -1
  230. package/src/components/ui/property-mobile-estimate-line-chart.tsx +3 -1
  231. package/src/components/ui/sidebar-nav.tsx +516 -0
  232. package/src/components/ui/stepper.tsx +347 -0
  233. package/src/components/ui/toggle.tsx +4 -4
  234. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +3 -1
  235. package/src/components/ui/transactions-income-expense-bar-chart.tsx +12 -9
  236. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +3 -1
  237. package/src/lib/format-currency.ts +44 -0
  238. package/src/lib/format-date.ts +50 -0
  239. package/src/lib/opportunity-constants.ts +12 -0
  240. package/src/lib/typography.ts +11 -11
  241. package/src/styles/globals.css +36 -34
  242. package/src/styles/styles-css.ts +1 -1
  243. package/tsup.config.ts +17 -0
  244. package/dist/chunk-PG6K5XEC.mjs +0 -475
  245. package/dist/chunk-WA6O6EUR.mjs +0 -1885
  246. package/dist/chunk-WNGWBVLV.mjs +0 -148
  247. package/dist/{chunk-LLVQKSU3.mjs → chunk-GD4BJDJR.mjs} +3 -3
@@ -19,6 +19,7 @@ import * as React from "react";
19
19
  import {
20
20
  type ColumnDef,
21
21
  type ColumnFiltersState,
22
+ type PaginationState,
22
23
  type SortingState,
23
24
  type VisibilityState,
24
25
  type RowSelectionState,
@@ -34,10 +35,6 @@ import {
34
35
  ArrowUpDown,
35
36
  ArrowUp,
36
37
  ArrowDown,
37
- ChevronLeftIcon,
38
- ChevronRightIcon,
39
- ChevronsLeftIcon,
40
- ChevronsRightIcon,
41
38
  SlidersHorizontal,
42
39
  } from "lucide-react";
43
40
  import { cn } from "@/lib/utils";
@@ -59,6 +56,7 @@ import {
59
56
  SelectTrigger,
60
57
  SelectValue,
61
58
  } from "@/components/ui/select";
59
+ import { PaginationNavButtons } from "@/components/ui/pagination";
62
60
  import {
63
61
  DropdownMenu,
64
62
  DropdownMenuCheckboxItem,
@@ -74,7 +72,7 @@ import { Skeleton } from "@/components/ui/skeleton";
74
72
  // Types
75
73
  // ---------------------------------------------------------------------------
76
74
 
77
- export interface DataTableProps<TData, TValue> {
75
+ interface DataTableProps<TData, TValue> {
78
76
  /** Column definitions — pass ColumnDef[] from \@tanstack/react-table */
79
77
  columns: ColumnDef<TData, TValue>[];
80
78
  /** Row data */
@@ -101,13 +99,35 @@ export interface DataTableProps<TData, TValue> {
101
99
  toolbar?: (table: TanstackTable<TData>) => React.ReactNode;
102
100
  /** Text shown when no results found */
103
101
  emptyText?: string;
102
+ /** Enable server-side pagination (disables client-side pagination model) */
103
+ manualPagination?: boolean;
104
+ /** Enable server-side sorting (disables client-side sorting model) */
105
+ manualSorting?: boolean;
106
+ /** Enable server-side filtering (disables client-side filtering model) */
107
+ manualFiltering?: boolean;
108
+ /** Total page count (required for server-side pagination) */
109
+ pageCount?: number;
110
+ /** Total row count (alternative to pageCount for server-side pagination) */
111
+ rowCount?: number;
112
+ /** Controlled sorting state */
113
+ sorting?: SortingState;
114
+ /** Controlled pagination state */
115
+ pagination?: PaginationState;
116
+ /** Controlled column filters state */
117
+ columnFilters?: ColumnFiltersState;
118
+ /** Callback when sorting state changes */
119
+ onSortingChange?: (sorting: SortingState) => void;
120
+ /** Callback when pagination state changes */
121
+ onPaginationChange?: (pagination: PaginationState) => void;
122
+ /** Callback when column filters state changes */
123
+ onColumnFiltersChange?: (filters: ColumnFiltersState) => void;
104
124
  }
105
125
 
106
126
  // ---------------------------------------------------------------------------
107
127
  // Helper: sortable header
108
128
  // ---------------------------------------------------------------------------
109
129
 
110
- export function DataTableColumnHeader({
130
+ function DataTableColumnHeader({
111
131
  column,
112
132
  title,
113
133
  className,
@@ -157,7 +177,7 @@ export function DataTableColumnHeader({
157
177
  // Helper: selection column
158
178
  // ---------------------------------------------------------------------------
159
179
 
160
- export function getSelectionColumn<TData>(): ColumnDef<TData> {
180
+ function getSelectionColumn<TData>(): ColumnDef<TData> {
161
181
  return {
162
182
  id: "select",
163
183
  header: ({ table }) => (
@@ -268,32 +288,30 @@ function DataTablePagination<TData>({
268
288
  }): ReactElement {
269
289
  return (
270
290
  <div
271
- className="flex items-center justify-between gap-4 py-4"
291
+ className="flex items-center gap-4 py-4"
272
292
  data-slot="data-table-pagination"
273
293
  >
274
- {/* Selected rows info */}
275
- <p className="text-body-small text-muted-foreground">
276
- {table.getFilteredSelectedRowModel().rows.length > 0 && (
277
- <>
278
- {table.getFilteredSelectedRowModel().rows.length} of{" "}
279
- {table.getFilteredRowModel().rows.length} row(s) selected.
280
- </>
281
- )}
282
- </p>
294
+ {table.getFilteredSelectedRowModel().rows.length > 0 && (
295
+ <span className="text-body-small text-muted-foreground">
296
+ {table.getFilteredSelectedRowModel().rows.length} of{" "}
297
+ {table.getFilteredRowModel().rows.length} row(s) selected.
298
+ </span>
299
+ )}
283
300
 
284
- <div className="flex items-center gap-6">
285
- {/* Page size selector */}
301
+ <div className="ml-auto flex items-center gap-6">
286
302
  <div className="flex items-center gap-2">
287
- <p className="text-body-small text-muted-foreground whitespace-nowrap">
303
+ <span className="text-body-small text-muted-foreground whitespace-nowrap">
288
304
  Rows per page
289
- </p>
305
+ </span>
290
306
  <Select
291
- onValueChange={(value) => {
292
- table.setPageSize(Number(value));
293
- }}
307
+ onValueChange={(value) => table.setPageSize(Number(value))}
294
308
  value={`${table.getState().pagination.pageSize}`}
295
309
  >
296
- <SelectTrigger className="w-[70px]" size="sm">
310
+ <SelectTrigger
311
+ aria-label="Rows per page"
312
+ className="w-[70px]"
313
+ size="sm"
314
+ >
297
315
  <SelectValue
298
316
  placeholder={`${table.getState().pagination.pageSize}`}
299
317
  />
@@ -308,59 +326,19 @@ function DataTablePagination<TData>({
308
326
  </Select>
309
327
  </div>
310
328
 
311
- {/* Page info */}
312
- <p className="text-body-small text-muted-foreground whitespace-nowrap">
329
+ <span className="text-body-small text-muted-foreground whitespace-nowrap">
313
330
  Page {table.getState().pagination.pageIndex + 1} of{" "}
314
331
  {table.getPageCount()}
315
- </p>
332
+ </span>
316
333
 
317
- {/* Page navigation */}
318
- <div className="flex items-center gap-1">
319
- <Button
320
- aria-label="Go to first page"
321
- disabled={!table.getCanPreviousPage()}
322
- onClick={() => {
323
- table.setPageIndex(0);
324
- }}
325
- size="icon-sm"
326
- variant="outline"
327
- >
328
- <ChevronsLeftIcon className="size-4" />
329
- </Button>
330
- <Button
331
- aria-label="Go to previous page"
332
- disabled={!table.getCanPreviousPage()}
333
- onClick={() => {
334
- table.previousPage();
335
- }}
336
- size="icon-sm"
337
- variant="outline"
338
- >
339
- <ChevronLeftIcon className="size-4" />
340
- </Button>
341
- <Button
342
- aria-label="Go to next page"
343
- disabled={!table.getCanNextPage()}
344
- onClick={() => {
345
- table.nextPage();
346
- }}
347
- size="icon-sm"
348
- variant="outline"
349
- >
350
- <ChevronRightIcon className="size-4" />
351
- </Button>
352
- <Button
353
- aria-label="Go to last page"
354
- disabled={!table.getCanNextPage()}
355
- onClick={() => {
356
- table.setPageIndex(table.getPageCount() - 1);
357
- }}
358
- size="icon-sm"
359
- variant="outline"
360
- >
361
- <ChevronsRightIcon className="size-4" />
362
- </Button>
363
- </div>
334
+ <PaginationNavButtons
335
+ hasNext={table.getCanNextPage()}
336
+ hasPrev={table.getCanPreviousPage()}
337
+ onFirst={() => table.setPageIndex(0)}
338
+ onLast={() => table.setPageIndex(table.getPageCount() - 1)}
339
+ onNext={() => table.nextPage()}
340
+ onPrev={() => table.previousPage()}
341
+ />
364
342
  </div>
365
343
  </div>
366
344
  );
@@ -412,15 +390,38 @@ function DataTable<TData, TValue>({
412
390
  className,
413
391
  toolbar,
414
392
  emptyText = "No results.",
393
+ manualPagination = false,
394
+ manualSorting = false,
395
+ manualFiltering = false,
396
+ pageCount,
397
+ rowCount,
398
+ sorting: controlledSorting,
399
+ pagination: controlledPagination,
400
+ columnFilters: controlledColumnFilters,
401
+ onSortingChange: onSortingChangeProp,
402
+ onPaginationChange: onPaginationChangeProp,
403
+ onColumnFiltersChange: onColumnFiltersChangeProp,
415
404
  }: DataTableProps<TData, TValue>): ReactElement {
416
- const [sorting, setSorting] = React.useState<SortingState>([]);
417
- const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
405
+ // Internal state (used when not externally controlled)
406
+ const [internalSorting, setInternalSorting] = React.useState<SortingState>(
418
407
  [],
419
408
  );
409
+ const [internalColumnFilters, setInternalColumnFilters] =
410
+ React.useState<ColumnFiltersState>([]);
411
+ const [internalPagination, setInternalPagination] =
412
+ React.useState<PaginationState>({
413
+ pageIndex: 0,
414
+ pageSize: pageSizeOptions[0] ?? 10,
415
+ });
420
416
  const [columnVisibility, setColumnVisibility] =
421
417
  React.useState<VisibilityState>({});
422
418
  const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
423
419
 
420
+ // Resolve controlled vs uncontrolled
421
+ const sorting = controlledSorting ?? internalSorting;
422
+ const columnFilters = controlledColumnFilters ?? internalColumnFilters;
423
+ const pagination = controlledPagination ?? internalPagination;
424
+
424
425
  // Prepend selection column if enabled
425
426
  const resolvedColumns = React.useMemo(() => {
426
427
  if (!enableRowSelection) return userColumns;
@@ -438,9 +439,25 @@ function DataTable<TData, TValue>({
438
439
  columnFilters,
439
440
  columnVisibility,
440
441
  rowSelection,
442
+ pagination,
443
+ },
444
+ onSortingChange: (updater) => {
445
+ const next = typeof updater === "function" ? updater(sorting) : updater;
446
+ if (controlledSorting === undefined) setInternalSorting(next);
447
+ onSortingChangeProp?.(next);
448
+ },
449
+ onColumnFiltersChange: (updater) => {
450
+ const next =
451
+ typeof updater === "function" ? updater(columnFilters) : updater;
452
+ if (controlledColumnFilters === undefined) setInternalColumnFilters(next);
453
+ onColumnFiltersChangeProp?.(next);
454
+ },
455
+ onPaginationChange: (updater) => {
456
+ const next =
457
+ typeof updater === "function" ? updater(pagination) : updater;
458
+ if (controlledPagination === undefined) setInternalPagination(next);
459
+ onPaginationChangeProp?.(next);
441
460
  },
442
- onSortingChange: setSorting,
443
- onColumnFiltersChange: setColumnFilters,
444
461
  onColumnVisibilityChange: setColumnVisibility,
445
462
  onRowSelectionChange: (updater) => {
446
463
  const next =
@@ -449,9 +466,17 @@ function DataTable<TData, TValue>({
449
466
  onRowSelectionChange?.(next);
450
467
  },
451
468
  getCoreRowModel: getCoreRowModel(),
452
- getFilteredRowModel: getFilteredRowModel(),
453
- getPaginationRowModel: getPaginationRowModel(),
454
- getSortedRowModel: getSortedRowModel(),
469
+ ...(manualFiltering
470
+ ? { manualFiltering: true as const }
471
+ : { getFilteredRowModel: getFilteredRowModel() }),
472
+ ...(manualPagination
473
+ ? { manualPagination: true as const }
474
+ : { getPaginationRowModel: getPaginationRowModel() }),
475
+ ...(manualSorting
476
+ ? { manualSorting: true as const }
477
+ : { getSortedRowModel: getSortedRowModel() }),
478
+ ...(pageCount !== undefined && { pageCount }),
479
+ ...(rowCount !== undefined && { rowCount }),
455
480
  enableRowSelection,
456
481
  });
457
482
 
@@ -539,11 +564,20 @@ function DataTable<TData, TValue>({
539
564
  );
540
565
  }
541
566
 
542
- export { DataTable, DataTableToolbar, DataTablePagination, DataTableSkeleton };
567
+ export {
568
+ DataTable,
569
+ DataTableToolbar,
570
+ DataTablePagination,
571
+ DataTableSkeleton,
572
+ DataTableColumnHeader,
573
+ getSelectionColumn,
574
+ };
543
575
 
544
576
  // Re-export tanstack types for consumer convenience
545
577
  export type {
578
+ DataTableProps,
546
579
  ColumnDef,
580
+ PaginationState,
547
581
  SortingState,
548
582
  ColumnFiltersState,
549
583
  VisibilityState,
@@ -1,15 +1,16 @@
1
- import { type ReactElement } from "react"
2
- import * as React from "react"
3
- import { format } from "date-fns"
4
- import { CalendarIcon } from "lucide-react"
5
- import { cn } from "@/lib/utils"
6
- import { Button } from "@/components/ui/button"
7
- import { Calendar } from "@/components/ui/calendar"
1
+ import { type ReactElement } from "react";
2
+ import * as React from "react";
3
+ import { format } from "date-fns";
4
+ import { CalendarIcon } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Input } from "@/components/ui/input";
8
+ import { Calendar } from "@/components/ui/calendar";
8
9
  import {
9
10
  Popover,
10
11
  PopoverContent,
11
12
  PopoverTrigger,
12
- } from "@/components/ui/popover"
13
+ } from "@/components/ui/popover";
13
14
 
14
15
  /**
15
16
  * DatePicker — WealthX Design System
@@ -22,18 +23,18 @@ import {
22
23
  */
23
24
 
24
25
  export interface DatePickerProps {
25
- value?: Date
26
- onChange?: (date: Date | undefined) => void
27
- placeholder?: string
26
+ value?: Date;
27
+ onChange?: (date: Date | undefined) => void;
28
+ placeholder?: string;
28
29
  /** Show a time input below the calendar */
29
- showTimePicker?: boolean
30
- disabled?: boolean
31
- className?: string
30
+ showTimePicker?: boolean;
31
+ disabled?: boolean;
32
+ className?: string;
32
33
  /** Passed through to Calendar (e.g. fromYear, toYear, captionLayout) */
33
34
  calendarProps?: Omit<
34
35
  React.ComponentProps<typeof Calendar>,
35
36
  "mode" | "selected" | "onSelect"
36
- >
37
+ >;
37
38
  }
38
39
 
39
40
  function DatePicker({
@@ -45,37 +46,37 @@ function DatePicker({
45
46
  className,
46
47
  calendarProps,
47
48
  }: DatePickerProps): ReactElement {
48
- const [open, setOpen] = React.useState(false)
49
+ const [open, setOpen] = React.useState(false);
49
50
 
50
51
  function handleDaySelect(day: Date | undefined): void {
51
52
  if (!day) {
52
- onChange?.(undefined)
53
- return
53
+ onChange?.(undefined);
54
+ return;
54
55
  }
55
56
  // Preserve existing time when selecting a new day
56
57
  if (showTimePicker && value) {
57
- day.setHours(value.getHours(), value.getMinutes())
58
+ day.setHours(value.getHours(), value.getMinutes());
58
59
  }
59
- onChange?.(day)
60
- if (!showTimePicker) setOpen(false)
60
+ onChange?.(day);
61
+ if (!showTimePicker) setOpen(false);
61
62
  }
62
63
 
63
64
  function handleTimeChange(e: React.ChangeEvent<HTMLInputElement>): void {
64
- const [hours, minutes] = e.target.value.split(":").map(Number)
65
- const next = value ? new Date(value) : new Date()
66
- next.setHours(hours, minutes, 0, 0)
67
- onChange?.(next)
65
+ const [hours, minutes] = e.target.value.split(":").map(Number);
66
+ const next = value ? new Date(value) : new Date();
67
+ next.setHours(hours, minutes, 0, 0);
68
+ onChange?.(next);
68
69
  }
69
70
 
70
71
  const timeValue = value
71
72
  ? `${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}`
72
- : ""
73
+ : "";
73
74
 
74
- let displayValue: string | undefined
75
+ let displayValue: string | undefined;
75
76
  if (value && showTimePicker) {
76
- displayValue = format(value, "dd/MM/yyyy HH:mm")
77
+ displayValue = format(value, "dd/MM/yyyy HH:mm");
77
78
  } else if (value) {
78
- displayValue = format(value, "dd/MM/yyyy")
79
+ displayValue = format(value, "dd/MM/yyyy");
79
80
  }
80
81
 
81
82
  return (
@@ -85,7 +86,9 @@ function DatePicker({
85
86
  <Button
86
87
  className={cn(
87
88
  "w-full justify-start rounded-none font-normal data-[empty=true]:text-muted-foreground",
88
- className
89
+ // Show open/focus state when the popover is expanded
90
+ "aria-expanded:border-ring aria-expanded:ring-2 aria-expanded:ring-ring/20",
91
+ className,
89
92
  )}
90
93
  data-empty={!value}
91
94
  data-slot="date-picker-trigger"
@@ -109,23 +112,25 @@ function DatePicker({
109
112
  {...calendarProps}
110
113
  className={cn(
111
114
  "rounded-none border-0 shadow-none",
112
- calendarProps?.className
115
+ calendarProps?.className,
113
116
  )}
114
117
  />
115
- {showTimePicker ? <div className="border-t border-border px-3 pb-3 pt-2">
118
+ {showTimePicker ? (
119
+ <div className="border-t border-border px-3 pb-3 pt-2">
116
120
  <label className="mb-1.5 block text-xs font-medium text-muted-foreground">
117
121
  Time
118
- <input
119
- className="mt-1.5 h-8 w-full rounded-none border border-input bg-transparent px-2 text-sm font-sans outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
122
+ <Input
123
+ className="mt-1.5 h-8"
120
124
  onChange={handleTimeChange}
121
125
  type="time"
122
126
  value={timeValue}
123
127
  />
124
128
  </label>
125
- </div> : null}
129
+ </div>
130
+ ) : null}
126
131
  </PopoverContent>
127
132
  </Popover>
128
- )
133
+ );
129
134
  }
130
135
 
131
- export { DatePicker }
136
+ export { DatePicker };
@@ -68,10 +68,55 @@ function DialogOverlay({
68
68
  );
69
69
  }
70
70
 
71
+ /**
72
+ * Maps the `size` prop to an exact CSS max-width value.
73
+ * Applied via inline style — bypasses Tailwind v4 class scanning entirely.
74
+ */
75
+ const DIALOG_MAX_WIDTHS: Record<string, string> = {
76
+ sm: "24rem",
77
+ md: "28rem",
78
+ lg: "32rem",
79
+ xl: "36rem",
80
+ "2xl": "42rem",
81
+ "3xl": "48rem",
82
+ "4xl": "56rem",
83
+ full: "100%",
84
+ };
85
+
86
+ /**
87
+ * Default minimum width for size="auto" dialogs.
88
+ * Prevents the dialog from collapsing too narrow when hugging content.
89
+ * Override per-instance via the `minWidth` prop.
90
+ */
91
+ const DIALOG_AUTO_MIN_WIDTH = "20rem"; // 320px
92
+
71
93
  export type DialogContentProps = React.ComponentProps<
72
94
  typeof DialogPrimitive.Popup
73
95
  > & {
74
96
  showCloseButton?: boolean;
97
+ /**
98
+ * Optional container element to render the Dialog portal into.
99
+ * Pass a ref'd element to scope the overlay inside a parent (e.g. a Sheet drawer)
100
+ * instead of appending to document.body.
101
+ */
102
+ container?: HTMLElement | null;
103
+ /** Vertical alignment of the dialog. "top" anchors to top-4; "center" (default) vertically centers. */
104
+ align?: "center" | "top";
105
+ /**
106
+ * Width behaviour of the dialog panel.
107
+ * - Fixed sizes ("sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "full"):
108
+ * dialog fills width up to that maximum (applied via inline style — reliable in Tailwind v4).
109
+ * - "auto": dialog shrinks to fit its content (hug-content). Has a built-in minimum
110
+ * width of `DIALOG_AUTO_MIN_WIDTH` (20rem / 320px) to avoid collapsing too narrow.
111
+ * Defaults to "lg".
112
+ */
113
+ size?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "full" | "auto";
114
+ /**
115
+ * Override the minimum width for `size="auto"` dialogs.
116
+ * Accepts any valid CSS length value (e.g. "24rem", "400px").
117
+ * Ignored when using a fixed size.
118
+ */
119
+ minWidth?: string;
75
120
  };
76
121
 
77
122
  function DialogContent({
@@ -79,28 +124,47 @@ function DialogContent({
79
124
  children,
80
125
  showCloseButton = true,
81
126
  style,
127
+ container,
128
+ align = "center",
129
+ size = "lg",
130
+ minWidth,
82
131
  ...props
83
132
  }: DialogContentProps): ReactElement {
84
133
  const themeVars = useThemeVars();
134
+ const isAuto = size === "auto";
135
+
136
+ const sizeStyle = isAuto
137
+ ? { minWidth: minWidth ?? DIALOG_AUTO_MIN_WIDTH }
138
+ : { maxWidth: DIALOG_MAX_WIDTHS[size] };
139
+
85
140
  return (
86
- <DialogPortal>
141
+ <DialogPortal container={container ?? undefined}>
87
142
  <DialogOverlay style={themeVars as React.CSSProperties} />
88
143
  <DialogPrimitive.Popup
89
144
  className={cn(
90
- // WealthX: removed rounded-lg (sharp corners), shadow-lg (flat panels), foreground/50 scrim via DialogOverlay
91
- "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 outline-none 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 sm:max-w-lg",
145
+ // WealthX: removed rounded-lg (sharp corners), shadow-lg (flat panels)
146
+ // max-w-[calc(100%-2rem)] acts as a viewport-edge guard on all sizes.
147
+ // Fixed max-width is applied via inline style (sizeStyle) to avoid
148
+ // Tailwind v4 class-scanning gaps with dynamic class lookups.
149
+ "fixed left-[50%] z-50 grid max-w-[calc(100%-2rem)] translate-x-[-50%] gap-4 border bg-background p-6 duration-200 outline-none 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",
150
+ // "auto" → hug content (with min-width floor); fixed sizes → fill to max-width
151
+ isAuto ? "w-auto" : "w-full",
152
+ align === "center"
153
+ ? "top-[50%] translate-y-[-50%]"
154
+ : "top-4 translate-y-0",
92
155
  className,
93
156
  )}
94
157
  data-slot="dialog-content"
95
- style={{ ...themeVars, ...style } as React.CSSProperties}
158
+ style={{ ...themeVars, ...sizeStyle, ...style } as React.CSSProperties}
96
159
  {...props}
97
160
  >
98
161
  {children}
99
162
  {showCloseButton ? (
100
163
  <DialogPrimitive.Close
101
- className="absolute top-4 right-4 transition-colors hover:bg-foreground/5 focus:outline-hidden focus:ring-2 focus:ring-border focus:ring-offset-0 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
164
+ className="absolute top-4 right-4 inline-flex size-7 items-center justify-center transition-colors hover:bg-foreground/5 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border focus-visible:ring-offset-0 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
102
165
  // WealthX: removed rounded-xs (sharp), replaced opacity fade with hover:bg-foreground/5,
103
- // focus ring uses border color (subtle, matches Figma DialogClose focus state)
166
+ // focus ring uses border color (subtle, matches Figma DialogClose focus state).
167
+ // size-7 = 28px hit area (icon is size-4=16px, centered via inline-flex).
104
168
  data-slot="dialog-close"
105
169
  >
106
170
  <XIcon />
@@ -152,6 +216,8 @@ function DialogFooter({
152
216
  {showCloseButton ? (
153
217
  <DialogPrimitive.Close
154
218
  className={cn(buttonVariants({ variant: "outline" }))}
219
+ // type="button" prevents accidental form submission inside Dialog forms
220
+ type="button"
155
221
  >
156
222
  Close
157
223
  </DialogPrimitive.Close>
@@ -30,7 +30,14 @@ import {
30
30
  type ChartGranularity,
31
31
  } from "./chart-shared";
32
32
 
33
- ChartJS.register(CategoryScale, LinearScale, BarController, BarElement, Tooltip, Legend);
33
+ ChartJS.register(
34
+ CategoryScale,
35
+ LinearScale,
36
+ BarController,
37
+ BarElement,
38
+ Tooltip,
39
+ Legend,
40
+ );
34
41
 
35
42
  // ---------------------------------------------------------------------------
36
43
  // Types
@@ -234,7 +241,9 @@ export function ExpenseBarChart({
234
241
  style={{ maxWidth: width, fontFamily }}
235
242
  >
236
243
  <CardHeader className="px-3 sm:px-6">
237
- <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
244
+ <CardTitle className="text-xs font-semibold uppercase tracking-wide">
245
+ {title}
246
+ </CardTitle>
238
247
  <CardAction>
239
248
  <div className="flex gap-0.5 sm:gap-1">
240
249
  {periods.map((p) => (