@wealthx/shadcn 1.2.2 → 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 (229) hide show
  1. package/.turbo/turbo-build.log +200 -156
  2. package/CHANGELOG.md +22 -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-VGSESELX.mjs → chunk-5FQIKDKP.mjs} +5 -5
  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-WAZD7NFU.mjs → chunk-BKNFWEH2.mjs} +6 -6
  20. package/dist/{chunk-CLIN5525.mjs → chunk-C7CQJNMR.mjs} +1 -1
  21. package/dist/{chunk-D4ILTPOG.mjs → chunk-CFMQP5QS.mjs} +5 -4
  22. package/dist/{chunk-VPBN3WOO.mjs → chunk-DGHAXJBN.mjs} +9 -7
  23. package/dist/chunk-DOEO3CDL.mjs +27 -0
  24. package/dist/{chunk-5MEWU56Z.mjs → chunk-DUJTAXMH.mjs} +11 -6
  25. package/dist/{chunk-GGM2UYGG.mjs → chunk-EBXQWIYG.mjs} +10 -4
  26. package/dist/chunk-EWRB4PAD.mjs +468 -0
  27. package/dist/{chunk-ZSHYDDRB.mjs → chunk-FAKPBKLT.mjs} +6 -2
  28. package/dist/chunk-FNQXOAYJ.mjs +169 -0
  29. package/dist/{chunk-A6AAWBPF.mjs → chunk-GHC7LLUX.mjs} +13 -4
  30. package/dist/chunk-HBZLGDIN.mjs +507 -0
  31. package/dist/{chunk-SIZMLSRU.mjs → chunk-HISNT2MG.mjs} +8 -6
  32. package/dist/{chunk-CGH4DRNG.mjs → chunk-HVY6KCCF.mjs} +10 -7
  33. package/dist/chunk-I3RZS7V2.mjs +136 -0
  34. package/dist/chunk-IAE3F7DR.mjs +1962 -0
  35. package/dist/{chunk-UT4KJR7V.mjs → chunk-IHMFS7NZ.mjs} +35 -74
  36. package/dist/{chunk-PCPLO5HT.mjs → chunk-IOJRDS6V.mjs} +96 -14
  37. package/dist/{chunk-LHYCMLVA.mjs → chunk-JKGDCQTZ.mjs} +11 -4
  38. package/dist/{chunk-H45TKD34.mjs → chunk-JMHR3YGZ.mjs} +1 -1
  39. package/dist/{chunk-4MN6UQHG.mjs → chunk-K5A5L6T2.mjs} +17 -39
  40. package/dist/chunk-LV35NGVG.mjs +272 -0
  41. package/dist/{chunk-FZIXGLMV.mjs → chunk-M3FV7LOK.mjs} +5 -12
  42. package/dist/{chunk-FMAXJ2SI.mjs → chunk-MBON7YRJ.mjs} +1 -1
  43. package/dist/chunk-MIZQHHUO.mjs +441 -0
  44. package/dist/chunk-MN5NYQCL.mjs +29 -0
  45. package/dist/chunk-NL3ZO62D.mjs +31 -0
  46. package/dist/{chunk-Q76O3RIQ.mjs → chunk-NMOI6CQD.mjs} +1 -1
  47. package/dist/{chunk-P6AM5V7O.mjs → chunk-OODBHKG7.mjs} +1 -1
  48. package/dist/chunk-PBL4OQV2.mjs +283 -0
  49. package/dist/{chunk-Y4QFWRNR.mjs → chunk-PU4YZQXV.mjs} +17 -18
  50. package/dist/chunk-QMY3AZJH.mjs +80 -0
  51. package/dist/{chunk-BL3DXM2X.mjs → chunk-QZ4RE6NA.mjs} +11 -4
  52. package/dist/{chunk-VACKZOMY.mjs → chunk-R3VSPKNP.mjs} +3 -3
  53. package/dist/{chunk-OPNQAVVH.mjs → chunk-RJI6GKVF.mjs} +8 -6
  54. package/dist/{chunk-WG6JGJXB.mjs → chunk-T4BJLT57.mjs} +1 -1
  55. package/dist/chunk-UMTOX62O.mjs +415 -0
  56. package/dist/{chunk-7MMXNK3C.mjs → chunk-VLARHE5V.mjs} +8 -6
  57. package/dist/{chunk-2I5S2AMY.mjs → chunk-XREGSKX3.mjs} +2 -2
  58. package/dist/{chunk-JNQORUPP.mjs → chunk-YJG55G2H.mjs} +14 -11
  59. package/dist/{chunk-ZRSDX6OW.mjs → chunk-ZC45IGZO.mjs} +33 -30
  60. package/dist/components/ui/add-column-modal.js +42 -14
  61. package/dist/components/ui/add-column-modal.mjs +5 -5
  62. package/dist/components/ui/add-lead-modal.js +42 -11
  63. package/dist/components/ui/add-lead-modal.mjs +3 -3
  64. package/dist/components/ui/advisor-card.js +497 -0
  65. package/dist/components/ui/advisor-card.mjs +13 -0
  66. package/dist/components/ui/ai-assistant-drawer.js +11 -10
  67. package/dist/components/ui/ai-assistant-drawer.mjs +3 -3
  68. package/dist/components/ui/alert-dialog.js +2 -2
  69. package/dist/components/ui/alert-dialog.mjs +2 -2
  70. package/dist/components/ui/appointment-action-dialogs.js +1160 -0
  71. package/dist/components/ui/appointment-action-dialogs.mjs +23 -0
  72. package/dist/components/ui/appointment-availability-settings.js +1590 -0
  73. package/dist/components/ui/appointment-availability-settings.mjs +23 -0
  74. package/dist/components/ui/appointment-book-dialog.js +1744 -0
  75. package/dist/components/ui/appointment-book-dialog.mjs +27 -0
  76. package/dist/components/ui/appointment-calendar-view.js +833 -0
  77. package/dist/components/ui/appointment-calendar-view.mjs +14 -0
  78. package/dist/components/ui/appointment-detail-sheet.js +1517 -0
  79. package/dist/components/ui/appointment-detail-sheet.mjs +24 -0
  80. package/dist/components/ui/appointment-gmail-connect.js +467 -0
  81. package/dist/components/ui/appointment-gmail-connect.mjs +14 -0
  82. package/dist/components/ui/appointment-mini-card.js +345 -0
  83. package/dist/components/ui/appointment-mini-card.mjs +11 -0
  84. package/dist/components/ui/appointment-time-slot-picker.js +311 -0
  85. package/dist/components/ui/appointment-time-slot-picker.mjs +13 -0
  86. package/dist/components/ui/appointment-upcoming-card.js +1268 -0
  87. package/dist/components/ui/appointment-upcoming-card.mjs +21 -0
  88. package/dist/components/ui/backoffice-alert-history-chart.js +11 -5
  89. package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -4
  90. package/dist/components/ui/backoffice-alerts-chart.js +786 -0
  91. package/dist/components/ui/backoffice-alerts-chart.mjs +19 -0
  92. package/dist/components/ui/backoffice-connections-chart.js +817 -0
  93. package/dist/components/ui/backoffice-connections-chart.mjs +19 -0
  94. package/dist/components/ui/backoffice-contact-history-chart.js +11 -5
  95. package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -4
  96. package/dist/components/ui/badge.js +6 -6
  97. package/dist/components/ui/badge.mjs +1 -1
  98. package/dist/components/ui/borrowing-capacity-line-chart.js +30 -21
  99. package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -4
  100. package/dist/components/ui/button.js +2 -2
  101. package/dist/components/ui/button.mjs +1 -1
  102. package/dist/components/ui/calendar.js +2 -2
  103. package/dist/components/ui/calendar.mjs +2 -2
  104. package/dist/components/ui/card.js +1 -1
  105. package/dist/components/ui/card.mjs +1 -1
  106. package/dist/components/ui/cash-balance-line-chart.js +31 -23
  107. package/dist/components/ui/cash-balance-line-chart.mjs +5 -4
  108. package/dist/components/ui/cashflow-bar-chart.js +12 -5
  109. package/dist/components/ui/cashflow-bar-chart.mjs +5 -4
  110. package/dist/components/ui/chip.js +97 -18
  111. package/dist/components/ui/chip.mjs +3 -2
  112. package/dist/components/ui/color-picker.js +158 -28
  113. package/dist/components/ui/color-picker.mjs +3 -1
  114. package/dist/components/ui/data-table.js +140 -119
  115. package/dist/components/ui/data-table.mjs +3 -2
  116. package/dist/components/ui/date-picker.js +48 -27
  117. package/dist/components/ui/date-picker.mjs +4 -3
  118. package/dist/components/ui/dialog.js +37 -9
  119. package/dist/components/ui/dialog.mjs +2 -2
  120. package/dist/components/ui/expense-bar-chart.js +12 -5
  121. package/dist/components/ui/expense-bar-chart.mjs +5 -4
  122. package/dist/components/ui/field.mjs +2 -2
  123. package/dist/components/ui/financial-cards.js +322 -155
  124. package/dist/components/ui/financial-cards.mjs +5 -3
  125. package/dist/components/ui/financial-drawers.js +2 -2
  126. package/dist/components/ui/financial-drawers.mjs +3 -3
  127. package/dist/components/ui/financial-sections.js +14 -10
  128. package/dist/components/ui/financial-sections.mjs +6 -5
  129. package/dist/components/ui/income-bar-chart.js +12 -5
  130. package/dist/components/ui/income-bar-chart.mjs +5 -4
  131. package/dist/components/ui/input-group.js +2 -2
  132. package/dist/components/ui/input-group.mjs +2 -2
  133. package/dist/components/ui/kanban-column.js +52 -44
  134. package/dist/components/ui/kanban-column.mjs +7 -5
  135. package/dist/components/ui/opportunity-card.js +52 -44
  136. package/dist/components/ui/opportunity-card.mjs +6 -4
  137. package/dist/components/ui/opportunity-edit-modals.js +1367 -1263
  138. package/dist/components/ui/opportunity-edit-modals.mjs +8 -8
  139. package/dist/components/ui/opportunity-summary-tab.js +2744 -2157
  140. package/dist/components/ui/opportunity-summary-tab.mjs +14 -14
  141. package/dist/components/ui/page-header.js +92 -0
  142. package/dist/components/ui/page-header.mjs +8 -0
  143. package/dist/components/ui/page-top-bar.js +88 -0
  144. package/dist/components/ui/page-top-bar.mjs +8 -0
  145. package/dist/components/ui/pagination.js +303 -19
  146. package/dist/components/ui/pagination.mjs +11 -4
  147. package/dist/components/ui/pipeline-board.js +205 -191
  148. package/dist/components/ui/pipeline-board.mjs +9 -7
  149. package/dist/components/ui/pipeline-dialogs.js +114 -65
  150. package/dist/components/ui/pipeline-dialogs.mjs +7 -6
  151. package/dist/components/ui/pipeline-primitives.js +6 -6
  152. package/dist/components/ui/pipeline-primitives.mjs +2 -2
  153. package/dist/components/ui/property-cashflow-doughnut-chart.js +14 -12
  154. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -4
  155. package/dist/components/ui/property-debt-equity-doughnut-chart.js +14 -12
  156. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -4
  157. package/dist/components/ui/property-mobile-estimate-line-chart.js +16 -14
  158. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -4
  159. package/dist/components/ui/sidebar-nav.js +234 -95
  160. package/dist/components/ui/sidebar-nav.mjs +4 -1
  161. package/dist/components/ui/stage-timeline.js +6 -6
  162. package/dist/components/ui/stage-timeline.mjs +3 -3
  163. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +18 -16
  164. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -4
  165. package/dist/components/ui/transactions-income-expense-bar-chart.js +28 -12
  166. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -4
  167. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +18 -16
  168. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -4
  169. package/dist/index.js +12899 -9343
  170. package/dist/index.mjs +256 -190
  171. package/dist/styles.css +1 -1
  172. package/package.json +71 -1
  173. package/src/components/index.tsx +114 -9
  174. package/src/components/ui/add-column-modal.tsx +7 -7
  175. package/src/components/ui/add-lead-modal.tsx +6 -3
  176. package/src/components/ui/advisor-card.tsx +227 -0
  177. package/src/components/ui/ai-assistant-drawer.tsx +4 -3
  178. package/src/components/ui/appointment-action-dialogs.tsx +297 -0
  179. package/src/components/ui/appointment-availability-settings.tsx +645 -0
  180. package/src/components/ui/appointment-book-dialog.tsx +618 -0
  181. package/src/components/ui/appointment-calendar-view.tsx +510 -0
  182. package/src/components/ui/appointment-detail-sheet.tsx +415 -0
  183. package/src/components/ui/appointment-gmail-connect.tsx +188 -0
  184. package/src/components/ui/appointment-mini-card.tsx +104 -0
  185. package/src/components/ui/appointment-time-slot-picker.tsx +123 -0
  186. package/src/components/ui/appointment-upcoming-card.tsx +635 -0
  187. package/src/components/ui/backoffice-alert-history-chart.tsx +10 -2
  188. package/src/components/ui/backoffice-alerts-chart.tsx +312 -0
  189. package/src/components/ui/backoffice-connections-chart.tsx +339 -0
  190. package/src/components/ui/backoffice-contact-history-chart.tsx +10 -2
  191. package/src/components/ui/badge.tsx +12 -6
  192. package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -11
  193. package/src/components/ui/button.tsx +2 -2
  194. package/src/components/ui/card.tsx +1 -1
  195. package/src/components/ui/cash-balance-line-chart.tsx +4 -23
  196. package/src/components/ui/cashflow-bar-chart.tsx +9 -2
  197. package/src/components/ui/chart-shared.tsx +4 -11
  198. package/src/components/ui/chip.tsx +23 -19
  199. package/src/components/ui/color-picker.tsx +4 -2
  200. package/src/components/ui/data-table.tsx +28 -74
  201. package/src/components/ui/date-picker.tsx +42 -37
  202. package/src/components/ui/dialog.tsx +72 -6
  203. package/src/components/ui/expense-bar-chart.tsx +11 -2
  204. package/src/components/ui/financial-cards.tsx +99 -10
  205. package/src/components/ui/income-bar-chart.tsx +11 -2
  206. package/src/components/ui/opportunity-card.tsx +10 -39
  207. package/src/components/ui/opportunity-edit-modals.tsx +98 -36
  208. package/src/components/ui/opportunity-summary-tab.tsx +548 -232
  209. package/src/components/ui/page-header.tsx +57 -0
  210. package/src/components/ui/page-top-bar.tsx +48 -0
  211. package/src/components/ui/pagination.tsx +171 -22
  212. package/src/components/ui/pipeline-board.tsx +12 -5
  213. package/src/components/ui/property-cashflow-doughnut-chart.tsx +3 -1
  214. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +3 -1
  215. package/src/components/ui/property-mobile-estimate-line-chart.tsx +3 -1
  216. package/src/components/ui/sidebar-nav.tsx +36 -37
  217. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +3 -1
  218. package/src/components/ui/transactions-income-expense-bar-chart.tsx +12 -9
  219. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +3 -1
  220. package/src/lib/format-currency.ts +44 -0
  221. package/src/lib/format-date.ts +50 -0
  222. package/src/lib/opportunity-constants.ts +12 -0
  223. package/src/styles/globals.css +17 -15
  224. package/src/styles/styles-css.ts +1 -1
  225. package/tsup.config.ts +14 -0
  226. package/dist/chunk-S4QRUQNW.mjs +0 -475
  227. package/dist/chunk-URGMJAE3.mjs +0 -1885
  228. package/dist/chunk-WNGWBVLV.mjs +0 -148
  229. package/dist/{chunk-LLVQKSU3.mjs → chunk-GD4BJDJR.mjs} +3 -3
@@ -0,0 +1,57 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ /**
5
+ * PageHeader — WealthX Design System
6
+ *
7
+ * In-page heading block. Renders inside scrollable content — not part of
8
+ * the layout shell. Use for sections that need a visible title and optional
9
+ * description (e.g. "My Advisors", "How can we help?").
10
+ *
11
+ * For a sticky top-of-page bar with action buttons, use PageTopBar instead.
12
+ */
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export interface PageHeaderProps {
19
+ /** Main page / section title */
20
+ title: string;
21
+ /** Optional subtitle shown below the title */
22
+ description?: string;
23
+ /** Optional action elements rendered to the right of the title block */
24
+ actions?: React.ReactNode;
25
+ className?: string;
26
+ }
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Component
30
+ // ---------------------------------------------------------------------------
31
+
32
+ export function PageHeader({
33
+ title,
34
+ description,
35
+ actions,
36
+ className,
37
+ }: PageHeaderProps) {
38
+ return (
39
+ <div
40
+ data-slot="page-header"
41
+ className={cn("flex items-start justify-between gap-4", className)}
42
+ >
43
+ {/* Title + description */}
44
+ <div className="flex flex-col gap-0.5">
45
+ <h1 className="text-xl font-bold leading-tight">{title}</h1>
46
+ {description && (
47
+ <p className="text-sm text-muted-foreground">{description}</p>
48
+ )}
49
+ </div>
50
+
51
+ {/* Optional actions */}
52
+ {actions && (
53
+ <div className="flex shrink-0 items-center gap-2">{actions}</div>
54
+ )}
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,48 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ /**
5
+ * PageTopBar — WealthX Design System
6
+ *
7
+ * Sticky top-of-page bar. Sits at the top of the main content area,
8
+ * separated from the content by a bottom border. Holds the page title
9
+ * and action buttons.
10
+ *
11
+ * Used by: Loan CRM, Contact, AI Builder, and other data-heavy pages.
12
+ * For an in-page title block with a description, use PageHeader instead.
13
+ */
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Types
17
+ // ---------------------------------------------------------------------------
18
+
19
+ export interface PageTopBarProps {
20
+ /** Page title shown on the left */
21
+ title: string;
22
+ /** Optional action elements rendered to the right */
23
+ actions?: React.ReactNode;
24
+ className?: string;
25
+ }
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Component
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export function PageTopBar({ title, actions, className }: PageTopBarProps) {
32
+ return (
33
+ <div
34
+ data-slot="page-top-bar"
35
+ className={cn(
36
+ "flex shrink-0 items-center justify-between gap-4",
37
+ "border-b border-border bg-background px-6 py-3",
38
+ className,
39
+ )}
40
+ >
41
+ {/* Title */}
42
+ <h1 className="text-lg font-semibold leading-tight">{title}</h1>
43
+
44
+ {/* Optional actions */}
45
+ {actions && <div className="flex items-center gap-2">{actions}</div>}
46
+ </div>
47
+ );
48
+ }
@@ -1,12 +1,21 @@
1
- import { type ReactElement } from "react"
2
- import * as React from "react"
1
+ import { type ReactElement } from "react";
2
+ import * as React from "react";
3
3
  import {
4
4
  ChevronLeftIcon,
5
5
  ChevronRightIcon,
6
+ ChevronsLeftIcon,
7
+ ChevronsRightIcon,
6
8
  MoreHorizontalIcon,
7
- } from "lucide-react"
8
- import { cn } from "@/lib/utils"
9
- import { buttonVariants, type Button } from "@/components/ui/button"
9
+ } from "lucide-react";
10
+ import { cn } from "@/lib/utils";
11
+ import { buttonVariants, Button } from "@/components/ui/button";
12
+ import {
13
+ Select,
14
+ SelectContent,
15
+ SelectItem,
16
+ SelectTrigger,
17
+ SelectValue,
18
+ } from "@/components/ui/select";
10
19
 
11
20
  /**
12
21
  * Pagination — WealthX Design System
@@ -16,7 +25,7 @@ import { buttonVariants, type Button } from "@/components/ui/button"
16
25
  * WealthX overrides: none — ghost/outline variants match design system
17
26
  */
18
27
 
19
- export type PaginationProps = React.ComponentProps<"nav">
28
+ export type PaginationProps = React.ComponentProps<"nav">;
20
29
 
21
30
  function Pagination({ className, ...props }: PaginationProps): ReactElement {
22
31
  return (
@@ -27,10 +36,10 @@ function Pagination({ className, ...props }: PaginationProps): ReactElement {
27
36
  role="navigation"
28
37
  {...props}
29
38
  />
30
- )
39
+ );
31
40
  }
32
41
 
33
- export type PaginationContentProps = React.ComponentProps<"ul">
42
+ export type PaginationContentProps = React.ComponentProps<"ul">;
34
43
 
35
44
  function PaginationContent({
36
45
  className,
@@ -42,19 +51,19 @@ function PaginationContent({
42
51
  data-slot="pagination-content"
43
52
  {...props}
44
53
  />
45
- )
54
+ );
46
55
  }
47
56
 
48
- export type PaginationItemProps = React.ComponentProps<"li">
57
+ export type PaginationItemProps = React.ComponentProps<"li">;
49
58
 
50
59
  function PaginationItem({ ...props }: PaginationItemProps): ReactElement {
51
- return <li data-slot="pagination-item" {...props} />
60
+ return <li data-slot="pagination-item" {...props} />;
52
61
  }
53
62
 
54
63
  export type PaginationLinkProps = {
55
- isActive?: boolean
64
+ isActive?: boolean;
56
65
  } & Pick<React.ComponentProps<typeof Button>, "size"> &
57
- React.ComponentProps<"a">
66
+ React.ComponentProps<"a">;
58
67
 
59
68
  function PaginationLink({
60
69
  className,
@@ -71,16 +80,18 @@ function PaginationLink({
71
80
  variant: isActive ? "outline" : "ghost",
72
81
  size,
73
82
  }),
74
- className
83
+ className,
75
84
  )}
76
85
  data-active={isActive}
77
86
  data-slot="pagination-link"
78
87
  {...props}
79
88
  />
80
- )
89
+ );
81
90
  }
82
91
 
83
- export type PaginationPreviousProps = React.ComponentProps<typeof PaginationLink>
92
+ export type PaginationPreviousProps = React.ComponentProps<
93
+ typeof PaginationLink
94
+ >;
84
95
 
85
96
  function PaginationPrevious({
86
97
  className,
@@ -96,10 +107,10 @@ function PaginationPrevious({
96
107
  <ChevronLeftIcon />
97
108
  <span className="hidden sm:block">Previous</span>
98
109
  </PaginationLink>
99
- )
110
+ );
100
111
  }
101
112
 
102
- export type PaginationNextProps = React.ComponentProps<typeof PaginationLink>
113
+ export type PaginationNextProps = React.ComponentProps<typeof PaginationLink>;
103
114
 
104
115
  function PaginationNext({
105
116
  className,
@@ -115,10 +126,10 @@ function PaginationNext({
115
126
  <span className="hidden sm:block">Next</span>
116
127
  <ChevronRightIcon />
117
128
  </PaginationLink>
118
- )
129
+ );
119
130
  }
120
131
 
121
- export type PaginationEllipsisProps = React.ComponentProps<"span">
132
+ export type PaginationEllipsisProps = React.ComponentProps<"span">;
122
133
 
123
134
  function PaginationEllipsis({
124
135
  className,
@@ -134,7 +145,143 @@ function PaginationEllipsis({
134
145
  <MoreHorizontalIcon className="size-4" />
135
146
  <span className="sr-only">More pages</span>
136
147
  </span>
137
- )
148
+ );
149
+ }
150
+
151
+ export type PaginationNavButtonsProps = {
152
+ hasPrev: boolean;
153
+ hasNext: boolean;
154
+ onFirst: () => void;
155
+ onPrev: () => void;
156
+ onNext: () => void;
157
+ onLast: () => void;
158
+ };
159
+
160
+ function PaginationNavButtons({
161
+ hasPrev,
162
+ hasNext,
163
+ onFirst,
164
+ onPrev,
165
+ onNext,
166
+ onLast,
167
+ }: PaginationNavButtonsProps): ReactElement {
168
+ return (
169
+ <div className="flex items-center gap-1">
170
+ <Button
171
+ aria-label="Go to first page"
172
+ disabled={!hasPrev}
173
+ onClick={onFirst}
174
+ size="icon-sm"
175
+ variant="outline"
176
+ >
177
+ <ChevronsLeftIcon className="size-4" />
178
+ </Button>
179
+ <Button
180
+ aria-label="Go to previous page"
181
+ disabled={!hasPrev}
182
+ onClick={onPrev}
183
+ size="icon-sm"
184
+ variant="outline"
185
+ >
186
+ <ChevronLeftIcon className="size-4" />
187
+ </Button>
188
+ <Button
189
+ aria-label="Go to next page"
190
+ disabled={!hasNext}
191
+ onClick={onNext}
192
+ size="icon-sm"
193
+ variant="outline"
194
+ >
195
+ <ChevronRightIcon className="size-4" />
196
+ </Button>
197
+ <Button
198
+ aria-label="Go to last page"
199
+ disabled={!hasNext}
200
+ onClick={onLast}
201
+ size="icon-sm"
202
+ variant="outline"
203
+ >
204
+ <ChevronsRightIcon className="size-4" />
205
+ </Button>
206
+ </div>
207
+ );
208
+ }
209
+
210
+ export type TablePaginationProps = {
211
+ page: number;
212
+ pageCount: number;
213
+ pageSize: number;
214
+ pageSizeOptions?: number[];
215
+ onPageChange: (page: number) => void;
216
+ onPageSizeChange: (pageSize: number) => void;
217
+ selectedCount?: number;
218
+ totalCount?: number;
219
+ className?: string;
220
+ };
221
+
222
+ function TablePagination({
223
+ page,
224
+ pageCount,
225
+ pageSize,
226
+ pageSizeOptions = [10, 20, 30, 50],
227
+ onPageChange,
228
+ onPageSizeChange,
229
+ selectedCount,
230
+ totalCount,
231
+ className,
232
+ }: TablePaginationProps): ReactElement {
233
+ return (
234
+ <div
235
+ className={cn("flex items-center gap-4 py-4", className)}
236
+ data-slot="table-pagination"
237
+ >
238
+ {selectedCount != null && selectedCount > 0 && totalCount != null && (
239
+ <span className="text-body-small text-muted-foreground">
240
+ {selectedCount} of {totalCount} row(s) selected.
241
+ </span>
242
+ )}
243
+
244
+ <div className="ml-auto flex items-center gap-6">
245
+ <div className="flex items-center gap-2">
246
+ <span className="text-body-small text-muted-foreground whitespace-nowrap">
247
+ Rows per page
248
+ </span>
249
+ <Select
250
+ onValueChange={(value) => onPageSizeChange(Number(value))}
251
+ value={`${pageSize}`}
252
+ >
253
+ <SelectTrigger
254
+ aria-label="Rows per page"
255
+ className="w-[70px]"
256
+ size="sm"
257
+ >
258
+ <SelectValue />
259
+ </SelectTrigger>
260
+ <SelectContent>
261
+ {pageSizeOptions.map((size) => (
262
+ <SelectItem key={size} value={`${size}`}>
263
+ {size}
264
+ </SelectItem>
265
+ ))}
266
+ </SelectContent>
267
+ </Select>
268
+ </div>
269
+
270
+ <span className="text-body-small text-muted-foreground whitespace-nowrap">
271
+ Page {page} of {pageCount}
272
+ </span>
273
+
274
+ <PaginationNavButtons
275
+ hasNext={page < pageCount}
276
+ hasPrev={page > 1}
277
+ onFirst={() => onPageChange(1)}
278
+ onLast={() => onPageChange(pageCount)}
279
+ onNext={() => onPageChange(page + 1)}
280
+ onPrev={() => onPageChange(page - 1)}
281
+ />
282
+ </div>
283
+ </div>
284
+ );
138
285
  }
139
286
 
140
287
  export {
@@ -145,4 +292,6 @@ export {
145
292
  PaginationPrevious,
146
293
  PaginationNext,
147
294
  PaginationEllipsis,
148
- }
295
+ PaginationNavButtons,
296
+ TablePagination,
297
+ };
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { RefreshCw, Search, X } from "lucide-react";
3
+ import { Button } from "@/components/ui/button";
3
4
  import { cn } from "@/lib/utils";
4
5
  import { Input } from "@/components/ui/input";
5
6
  import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
@@ -128,13 +129,16 @@ function Toolbar({
128
129
  className="h-8 pl-8 text-sm"
129
130
  />
130
131
  {searchValue && (
131
- <button
132
+ <Button
133
+ type="button"
134
+ variant="ghost"
135
+ size="icon"
132
136
  onClick={() => onSearchChange("")}
133
- className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
137
+ className="absolute right-2 top-1/2 size-6 -translate-y-1/2 text-muted-foreground hover:text-foreground"
134
138
  aria-label="Clear search"
135
139
  >
136
140
  <X className="size-3.5" />
137
- </button>
141
+ </Button>
138
142
  )}
139
143
  </div>
140
144
 
@@ -163,13 +167,16 @@ function Toolbar({
163
167
 
164
168
  {/* Refresh */}
165
169
  {onRefresh && (
166
- <button
170
+ <Button
171
+ type="button"
172
+ variant="ghost"
173
+ size="icon"
167
174
  onClick={onRefresh}
168
175
  className="ml-auto text-muted-foreground hover:text-foreground"
169
176
  aria-label="Refresh board"
170
177
  >
171
178
  <RefreshCw className="size-4" />
172
- </button>
179
+ </Button>
173
180
  )}
174
181
  </div>
175
182
  );
@@ -127,7 +127,9 @@ export function PropertyCashflowDoughnutChart({
127
127
  style={{ maxWidth: width, fontFamily }}
128
128
  >
129
129
  <CardHeader className="px-3 sm:px-6">
130
- <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
130
+ <CardTitle className="text-xs font-semibold uppercase tracking-wide">
131
+ {title}
132
+ </CardTitle>
131
133
  </CardHeader>
132
134
 
133
135
  <CardContent className="px-3 sm:px-6">
@@ -124,7 +124,9 @@ export function PropertyDebtEquityDoughnutChart({
124
124
  style={{ maxWidth: width, fontFamily }}
125
125
  >
126
126
  <CardHeader className="px-3 sm:px-6">
127
- <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
127
+ <CardTitle className="text-xs font-semibold uppercase tracking-wide">
128
+ {title}
129
+ </CardTitle>
128
130
  </CardHeader>
129
131
 
130
132
  <CardContent className="px-3 sm:px-6">
@@ -337,7 +337,9 @@ export function PropertyMobileEstimateLineChart({
337
337
  style={{ maxWidth: width, fontFamily }}
338
338
  >
339
339
  <CardHeader className="px-3 sm:px-6">
340
- <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
340
+ <CardTitle className="text-xs font-semibold uppercase tracking-wide">
341
+ {title}
342
+ </CardTitle>
341
343
  {showPeriodSelector && (
342
344
  <CardAction>
343
345
  <div className="flex gap-0.5 sm:gap-1">
@@ -24,6 +24,8 @@ import {
24
24
  } from "lucide-react";
25
25
  import type { LucideIcon } from "lucide-react";
26
26
  import { cn } from "@/lib/utils";
27
+ import { formatCurrency } from "@/lib/format-currency";
28
+ import { Button } from "./button";
27
29
  import {
28
30
  Tooltip,
29
31
  TooltipContent,
@@ -106,19 +108,6 @@ function getInitials(name: string): string {
106
108
  .slice(0, 2);
107
109
  }
108
110
 
109
- function formatCurrency(value: number, isNetItem = false): string {
110
- const abs = Math.abs(value);
111
- const formatted = new Intl.NumberFormat("en-AU", {
112
- style: "currency",
113
- currency: "AUD",
114
- minimumFractionDigits: 0,
115
- maximumFractionDigits: 0,
116
- }).format(abs);
117
- if (!isNetItem) return formatted;
118
- if (value > 0) return `+${formatted}`;
119
- if (value < 0) return `-${formatted}`;
120
- return formatted;
121
- }
122
111
 
123
112
  function navIconCn(isActive: boolean): string {
124
113
  return cn(
@@ -186,7 +175,7 @@ function MetricsGroup({ group }: MetricsGroupProps) {
186
175
  item.isNetItem && item.value < 0 && "text-destructive",
187
176
  )}
188
177
  >
189
- {formatCurrency(item.value, item.isNetItem)}
178
+ {formatCurrency(item.value, { showSign: item.isNetItem })}
190
179
  </span>
191
180
  </div>
192
181
  ))}
@@ -211,16 +200,17 @@ function SidebarNavItemView({
211
200
 
212
201
  return (
213
202
  <NavTooltip label={item.title} collapsed={collapsed}>
214
- <button
203
+ <Button
215
204
  type="button"
205
+ variant="ghost"
216
206
  onClick={() => onNavigate?.(item.href)}
217
207
  className={cn(
218
- "group flex w-full items-center gap-3 py-2.5 text-base font-medium transition-colors",
208
+ "group h-auto w-full items-center gap-3 py-2.5 text-base font-medium transition-colors",
219
209
  "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
220
210
  collapsed
221
211
  ? "justify-center px-2"
222
212
  : cn(
223
- "px-3 border-l-4",
213
+ "justify-start px-3 border-l-4",
224
214
  item.isActive
225
215
  ? "bg-white/15 text-brand-secondary-foreground border-primary"
226
216
  : "border-transparent",
@@ -233,7 +223,7 @@ function SidebarNavItemView({
233
223
  strokeWidth={1.75}
234
224
  />
235
225
  {!collapsed && <span className="truncate">{item.title}</span>}
236
- </button>
226
+ </Button>
237
227
  </NavTooltip>
238
228
  );
239
229
  }
@@ -262,11 +252,12 @@ function CollapsibleNavItem({
262
252
  if (collapsed) {
263
253
  return (
264
254
  <NavTooltip label={item.title} collapsed={collapsed}>
265
- <button
255
+ <Button
266
256
  type="button"
257
+ variant="ghost"
267
258
  onClick={() => onNavigate?.(item.href)}
268
259
  className={cn(
269
- "group flex w-full items-center justify-center px-2 py-2.5 transition-colors",
260
+ "group h-auto w-full justify-center px-2 py-2.5 transition-colors",
270
261
  "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
271
262
  hasActiveChild && "bg-white/15 text-brand-secondary-foreground",
272
263
  )}
@@ -276,18 +267,19 @@ function CollapsibleNavItem({
276
267
  size={18}
277
268
  strokeWidth={1.75}
278
269
  />
279
- </button>
270
+ </Button>
280
271
  </NavTooltip>
281
272
  );
282
273
  }
283
274
 
284
275
  return (
285
276
  <div>
286
- <button
277
+ <Button
287
278
  type="button"
279
+ variant="ghost"
288
280
  onClick={() => setOpen((prev) => !prev)}
289
281
  className={cn(
290
- "group flex w-full items-center gap-3 px-3 py-2.5 text-base font-medium transition-colors",
282
+ "group h-auto w-full justify-start gap-3 px-3 py-2.5 text-base font-medium transition-colors",
291
283
  "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
292
284
  "border-l-4 border-transparent",
293
285
  hasActiveChild &&
@@ -308,17 +300,18 @@ function CollapsibleNavItem({
308
300
  size={14}
309
301
  strokeWidth={2}
310
302
  />
311
- </button>
303
+ </Button>
312
304
 
313
305
  {open && item.subItems && (
314
306
  <div className="ml-9 border-l border-white/15 pl-3">
315
307
  {item.subItems.map((sub) => (
316
- <button
308
+ <Button
317
309
  key={sub.href}
318
310
  type="button"
311
+ variant="ghost"
319
312
  onClick={() => onNavigate?.(sub.href)}
320
313
  className={cn(
321
- "flex w-full items-center gap-2 py-1.5 pl-1 text-sm transition-colors",
314
+ "h-auto w-full justify-start gap-2 py-1.5 pl-1 text-sm transition-colors",
322
315
  "text-brand-secondary-foreground/50 hover:text-brand-secondary-foreground",
323
316
  sub.isActive && "text-primary font-medium",
324
317
  )}
@@ -334,7 +327,7 @@ function CollapsibleNavItem({
334
327
  )}
335
328
  />
336
329
  <span className="truncate">{sub.title}</span>
337
- </button>
330
+ </Button>
338
331
  ))}
339
332
  </div>
340
333
  )}
@@ -364,7 +357,10 @@ export function SidebarNav({
364
357
  data-slot="sidebar-nav"
365
358
  data-collapsed={collapsed}
366
359
  className={cn(
367
- "flex h-full flex-col bg-brand-secondary text-brand-secondary-foreground",
360
+ // Force dark-mode CSS variable resolution — sidebar is always dark-backgrounded
361
+ // regardless of system theme, so semantic tokens (destructive, success, etc.)
362
+ // must use their dark-mode values to maintain WCAG contrast.
363
+ "dark flex h-full flex-col bg-brand-secondary text-brand-secondary-foreground",
368
364
  "transition-all duration-200 ease-in-out",
369
365
  collapsed ? "w-14" : "w-[279px]",
370
366
  className,
@@ -395,11 +391,12 @@ export function SidebarNav({
395
391
  {/* User section */}
396
392
  <div className="border-b border-white/15">
397
393
  <NavTooltip label={userName} collapsed={collapsed}>
398
- <button
394
+ <Button
399
395
  type="button"
396
+ variant="ghost"
400
397
  onClick={() => !collapsed && setUserMenuOpen((prev) => !prev)}
401
398
  className={cn(
402
- "group flex w-full items-center gap-3 px-5 py-5 text-base transition-colors",
399
+ "group h-auto w-full justify-start gap-3 px-5 py-5 text-base transition-colors",
403
400
  "text-brand-secondary-foreground hover:bg-white/10",
404
401
  collapsed && "justify-center px-2 py-4",
405
402
  )}
@@ -422,17 +419,18 @@ export function SidebarNav({
422
419
  />
423
420
  </>
424
421
  )}
425
- </button>
422
+ </Button>
426
423
  </NavTooltip>
427
424
 
428
425
  {/* Logout dropdown */}
429
426
  {!collapsed && userMenuOpen && (
430
427
  <div className="border-t border-white/15 bg-black/20">
431
- <button
428
+ <Button
432
429
  type="button"
430
+ variant="ghost"
433
431
  onClick={onLogout}
434
432
  className={cn(
435
- "flex w-full items-center gap-3 px-5 py-3 text-base",
433
+ "h-auto w-full justify-start gap-3 px-5 py-3 text-base",
436
434
  "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground transition-colors",
437
435
  )}
438
436
  >
@@ -442,7 +440,7 @@ export function SidebarNav({
442
440
  className="shrink-0 text-destructive"
443
441
  />
444
442
  <span>Logout</span>
445
- </button>
443
+ </Button>
446
444
  </div>
447
445
  )}
448
446
  </div>
@@ -484,11 +482,12 @@ export function SidebarNav({
484
482
  label={collapsed ? "Expand" : "Collapse"}
485
483
  collapsed={collapsed}
486
484
  >
487
- <button
485
+ <Button
488
486
  type="button"
487
+ variant="ghost"
489
488
  onClick={() => onCollapsedChange(!collapsed)}
490
489
  className={cn(
491
- "flex w-full items-center gap-3 px-3 py-3 transition-colors",
490
+ "h-auto w-full justify-start gap-3 px-3 py-3 transition-colors",
492
491
  "text-brand-secondary-foreground/80 hover:bg-white/10 hover:text-brand-secondary-foreground",
493
492
  collapsed && "justify-center px-2",
494
493
  )}
@@ -507,7 +506,7 @@ export function SidebarNav({
507
506
  />
508
507
  )}
509
508
  {!collapsed && <span className="text-sm">Collapse</span>}
510
- </button>
509
+ </Button>
511
510
  </NavTooltip>
512
511
  </div>
513
512
  )}
@@ -129,7 +129,9 @@ export function TransactionsExpenseCategoriesDoughnutChart({
129
129
  style={{ maxWidth: width, fontFamily }}
130
130
  >
131
131
  <CardHeader className="px-3 sm:px-6">
132
- <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
132
+ <CardTitle className="text-xs font-semibold uppercase tracking-wide">
133
+ {title}
134
+ </CardTitle>
133
135
  </CardHeader>
134
136
 
135
137
  <CardContent className="px-3 sm:px-6">