@wealthx/shadcn 1.2.1 → 1.2.2

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 (58) hide show
  1. package/.turbo/turbo-build.log +76 -67
  2. package/CHANGELOG.md +7 -0
  3. package/dist/{chunk-4CX4SBRO.mjs → chunk-A6AAWBPF.mjs} +1 -1
  4. package/dist/{chunk-WOEHFRGB.mjs → chunk-BDYZCBRT.mjs} +4 -4
  5. package/dist/{chunk-PMB3A7V3.mjs → chunk-EI5F6FMT.mjs} +1 -1
  6. package/dist/{chunk-CSDO6VBW.mjs → chunk-LBMRIB3G.mjs} +10 -10
  7. package/dist/{chunk-PG6K5XEC.mjs → chunk-S4QRUQNW.mjs} +1 -1
  8. package/dist/chunk-U4NDAF2P.mjs +207 -0
  9. package/dist/{chunk-DOH3EHX7.mjs → chunk-U5X52X37.mjs} +1 -1
  10. package/dist/{chunk-WA6O6EUR.mjs → chunk-URGMJAE3.mjs} +9 -9
  11. package/dist/{chunk-ZRO5JO3H.mjs → chunk-UT4KJR7V.mjs} +48 -12
  12. package/dist/{chunk-SYOD63OZ.mjs → chunk-VGSESELX.mjs} +2 -2
  13. package/dist/chunk-ZRSDX6OW.mjs +385 -0
  14. package/dist/chunk-ZSHYDDRB.mjs +249 -0
  15. package/dist/components/ui/add-column-modal.mjs +3 -3
  16. package/dist/components/ui/add-lead-modal.mjs +2 -2
  17. package/dist/components/ui/color-picker.js +417 -0
  18. package/dist/components/ui/color-picker.mjs +22 -0
  19. package/dist/components/ui/data-table.js +44 -12
  20. package/dist/components/ui/data-table.mjs +1 -1
  21. package/dist/components/ui/date-picker.mjs +2 -2
  22. package/dist/components/ui/form-primitives.js +4 -4
  23. package/dist/components/ui/form-primitives.mjs +3 -3
  24. package/dist/components/ui/opportunity-edit-modals.js +4 -4
  25. package/dist/components/ui/opportunity-edit-modals.mjs +8 -8
  26. package/dist/components/ui/opportunity-summary-tab.js +4 -4
  27. package/dist/components/ui/opportunity-summary-tab.mjs +9 -9
  28. package/dist/components/ui/pipeline-board.js +4 -4
  29. package/dist/components/ui/pipeline-board.mjs +3 -3
  30. package/dist/components/ui/pipeline-dialogs.js +4 -4
  31. package/dist/components/ui/pipeline-dialogs.mjs +5 -5
  32. package/dist/components/ui/sidebar-nav.js +540 -0
  33. package/dist/components/ui/sidebar-nav.mjs +11 -0
  34. package/dist/components/ui/stepper.js +283 -0
  35. package/dist/components/ui/stepper.mjs +18 -0
  36. package/dist/components/ui/toggle-group.js +4 -4
  37. package/dist/components/ui/toggle-group.mjs +2 -2
  38. package/dist/components/ui/toggle.js +4 -4
  39. package/dist/components/ui/toggle.mjs +1 -1
  40. package/dist/index.js +2141 -1292
  41. package/dist/index.mjs +103 -71
  42. package/dist/lib/typography.js +10 -10
  43. package/dist/lib/typography.mjs +1 -1
  44. package/dist/styles.css +1 -1
  45. package/package.json +16 -1
  46. package/src/components/index.tsx +41 -0
  47. package/src/components/ui/color-picker.tsx +307 -0
  48. package/src/components/ui/data-table.tsx +91 -11
  49. package/src/components/ui/sidebar-nav.tsx +517 -0
  50. package/src/components/ui/stepper.tsx +347 -0
  51. package/src/components/ui/toggle.tsx +4 -4
  52. package/src/lib/typography.ts +11 -11
  53. package/src/styles/globals.css +19 -19
  54. package/src/styles/styles-css.ts +1 -1
  55. package/tsup.config.ts +3 -0
  56. package/dist/{chunk-KUDCQ4FI.mjs → chunk-5MEWU56Z.mjs} +3 -3
  57. package/dist/{chunk-PR6V5XKM.mjs → chunk-CGH4DRNG.mjs} +3 -3
  58. package/dist/{chunk-3WMX6KWS.mjs → chunk-Y4QFWRNR.mjs} +8 -8
@@ -0,0 +1,517 @@
1
+ /**
2
+ * SidebarNav — WealthX Design System
3
+ *
4
+ * Presentational sidebar navigation organism.
5
+ * Used by both Backoffice (Wealth Pro) and Frontend (WealthX app).
6
+ *
7
+ * Background: bg-brand-secondary (tenant dark navy — set via ThemeProvider).
8
+ * Text: text-brand-brand-secondary-foreground (white on dark navy).
9
+ *
10
+ * - All icons must be Lucide icons (LucideIcon type).
11
+ * - Supports collapsible sub-items (accordion).
12
+ * - Collapsed state: icon-only, metrics hidden.
13
+ * - metricsGroups: optional financial summary rows (Frontend sidebar only).
14
+ * - No internal navigation — consumers wire onNavigate / onLogout.
15
+ */
16
+ import * as React from "react";
17
+ import {
18
+ ChevronDown,
19
+ ChevronRight,
20
+ Info,
21
+ LogOut,
22
+ PanelLeftClose,
23
+ PanelLeftOpen,
24
+ } from "lucide-react";
25
+ import type { LucideIcon } from "lucide-react";
26
+ import { cn } from "@/lib/utils";
27
+ import {
28
+ Tooltip,
29
+ TooltipContent,
30
+ TooltipProvider,
31
+ TooltipTrigger,
32
+ } from "@/components/ui/tooltip";
33
+
34
+ // ─── Types ─────────────────────────────────────────────────────────────────────
35
+
36
+ export interface SidebarNavSubItem {
37
+ title: string;
38
+ href: string;
39
+ isActive?: boolean;
40
+ }
41
+
42
+ export interface SidebarNavItem {
43
+ /** Lucide icon component */
44
+ icon: LucideIcon;
45
+ title: string;
46
+ href: string;
47
+ isActive?: boolean;
48
+ /** When true, renders an expandable group with subItems */
49
+ isCollapsible?: boolean;
50
+ subItems?: SidebarNavSubItem[];
51
+ }
52
+
53
+ export interface SidebarNavMetricItem {
54
+ name: string;
55
+ /** Numeric value in dollars — formatted as currency automatically */
56
+ value: number;
57
+ /** Net items show +/- prefix and a primary-color underline */
58
+ isNetItem?: boolean;
59
+ /** Optional tooltip text shown via Info icon */
60
+ info?: string;
61
+ }
62
+
63
+ export interface SidebarNavMetricsGroup {
64
+ items: SidebarNavMetricItem[];
65
+ }
66
+
67
+ export interface SidebarNavProps {
68
+ items: SidebarNavItem[];
69
+ /** Display name for the current user */
70
+ userName?: string;
71
+ /** Whether the sidebar is in narrow (icon-only) mode */
72
+ collapsed?: boolean;
73
+ /**
74
+ * Optional logo URL rendered at the top of the sidebar.
75
+ * Hidden when collapsed (unless logoCollapsed is provided).
76
+ * Use a horizontal/landscape logo (max-height 32px).
77
+ * The image is rendered white via CSS filter — use a logo with non-transparent paths.
78
+ */
79
+ logo?: string;
80
+ /**
81
+ * Optional icon-only logo URL shown when the sidebar is collapsed.
82
+ * Use a square icon variant of the logo (rendered at 32×32px, white filter applied).
83
+ */
84
+ logoCollapsed?: string;
85
+ /**
86
+ * Optional financial metric groups rendered between the user section and
87
+ * nav items. Hidden when collapsed. Used by the Frontend (WealthX app) sidebar.
88
+ */
89
+ metricsGroups?: SidebarNavMetricsGroup[];
90
+ onNavigate?: (href: string) => void;
91
+ onLogout?: () => void;
92
+ /** Called when the user clicks the expand/collapse toggle button at the bottom */
93
+ onCollapsedChange?: (collapsed: boolean) => void;
94
+ className?: string;
95
+ }
96
+
97
+ // ─── Helpers ───────────────────────────────────────────────────────────────────
98
+
99
+ function getInitials(name: string): string {
100
+ return name
101
+ .split(" ")
102
+ .filter(Boolean)
103
+ .map((word) => word[0])
104
+ .join("")
105
+ .toUpperCase()
106
+ .slice(0, 2);
107
+ }
108
+
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
+
123
+ function navIconCn(isActive: boolean): string {
124
+ return cn(
125
+ "shrink-0 transition-colors",
126
+ isActive
127
+ ? "text-primary"
128
+ : "text-brand-secondary-foreground/50 group-hover:text-brand-secondary-foreground",
129
+ );
130
+ }
131
+
132
+ // ─── NavTooltip ────────────────────────────────────────────────────────────────
133
+ // Wraps content in a right-side tooltip only when the sidebar is collapsed.
134
+
135
+ interface NavTooltipProps {
136
+ label: string;
137
+ collapsed: boolean;
138
+ children: React.ReactElement;
139
+ }
140
+
141
+ function NavTooltip({ label, collapsed, children }: NavTooltipProps) {
142
+ if (!collapsed) return children;
143
+ return (
144
+ <Tooltip>
145
+ <TooltipTrigger render={children} />
146
+ <TooltipContent side="right">{label}</TooltipContent>
147
+ </Tooltip>
148
+ );
149
+ }
150
+
151
+ // ─── MetricsGroup ──────────────────────────────────────────────────────────────
152
+
153
+ interface MetricsGroupProps {
154
+ group: SidebarNavMetricsGroup;
155
+ }
156
+
157
+ function MetricsGroup({ group }: MetricsGroupProps) {
158
+ return (
159
+ <div className="border-b border-white/15 py-4 px-5 flex flex-col gap-1.5">
160
+ {group.items.map((item) => (
161
+ <div
162
+ key={item.name}
163
+ className="flex items-center justify-between gap-2"
164
+ >
165
+ <div className="flex items-center gap-1 min-w-0">
166
+ <span
167
+ className={cn(
168
+ "text-sm truncate text-brand-secondary-foreground/80",
169
+ item.isNetItem &&
170
+ "font-semibold text-brand-secondary-foreground border-b-2 border-primary pb-px",
171
+ )}
172
+ >
173
+ {item.name}
174
+ </span>
175
+ {item.info && (
176
+ <Info
177
+ size={11}
178
+ strokeWidth={2}
179
+ className="shrink-0 text-brand-secondary-foreground/50"
180
+ />
181
+ )}
182
+ </div>
183
+ <span
184
+ className={cn(
185
+ "text-sm font-semibold tabular-nums shrink-0 text-brand-secondary-foreground",
186
+ item.isNetItem && item.value < 0 && "text-destructive",
187
+ )}
188
+ >
189
+ {formatCurrency(item.value, item.isNetItem)}
190
+ </span>
191
+ </div>
192
+ ))}
193
+ </div>
194
+ );
195
+ }
196
+
197
+ // ─── SidebarNavItemView (single link) ──────────────────────────────────────────
198
+
199
+ interface SidebarNavItemViewProps {
200
+ item: SidebarNavItem;
201
+ collapsed: boolean;
202
+ onNavigate?: (href: string) => void;
203
+ }
204
+
205
+ function SidebarNavItemView({
206
+ item,
207
+ collapsed,
208
+ onNavigate,
209
+ }: SidebarNavItemViewProps) {
210
+ const Icon = item.icon;
211
+
212
+ return (
213
+ <NavTooltip label={item.title} collapsed={collapsed}>
214
+ <button
215
+ type="button"
216
+ onClick={() => onNavigate?.(item.href)}
217
+ className={cn(
218
+ "group flex w-full items-center gap-3 py-2.5 text-base font-medium transition-colors",
219
+ "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
220
+ collapsed
221
+ ? "justify-center px-2"
222
+ : cn(
223
+ "px-3 border-l-4",
224
+ item.isActive
225
+ ? "bg-white/15 text-brand-secondary-foreground border-primary"
226
+ : "border-transparent",
227
+ ),
228
+ )}
229
+ >
230
+ <Icon
231
+ className={navIconCn(item.isActive ?? false)}
232
+ size={18}
233
+ strokeWidth={1.75}
234
+ />
235
+ {!collapsed && <span className="truncate">{item.title}</span>}
236
+ </button>
237
+ </NavTooltip>
238
+ );
239
+ }
240
+
241
+ // ─── CollapsibleNavItem (accordion group) ──────────────────────────────────────
242
+
243
+ interface CollapsibleNavItemProps {
244
+ item: SidebarNavItem;
245
+ collapsed: boolean;
246
+ onNavigate?: (href: string) => void;
247
+ }
248
+
249
+ function CollapsibleNavItem({
250
+ item,
251
+ collapsed,
252
+ onNavigate,
253
+ }: CollapsibleNavItemProps) {
254
+ const Icon = item.icon;
255
+ const hasActiveChild = item.subItems?.some((sub) => sub.isActive) ?? false;
256
+ const [open, setOpen] = React.useState(hasActiveChild);
257
+
258
+ React.useEffect(() => {
259
+ if (hasActiveChild) setOpen(true);
260
+ }, [hasActiveChild]);
261
+
262
+ if (collapsed) {
263
+ return (
264
+ <NavTooltip label={item.title} collapsed={collapsed}>
265
+ <button
266
+ type="button"
267
+ onClick={() => onNavigate?.(item.href)}
268
+ className={cn(
269
+ "group flex w-full items-center justify-center px-2 py-2.5 transition-colors",
270
+ "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
271
+ hasActiveChild && "bg-white/15 text-brand-secondary-foreground",
272
+ )}
273
+ >
274
+ <Icon
275
+ className={navIconCn(hasActiveChild)}
276
+ size={18}
277
+ strokeWidth={1.75}
278
+ />
279
+ </button>
280
+ </NavTooltip>
281
+ );
282
+ }
283
+
284
+ return (
285
+ <div>
286
+ <button
287
+ type="button"
288
+ onClick={() => setOpen((prev) => !prev)}
289
+ className={cn(
290
+ "group flex w-full items-center gap-3 px-3 py-2.5 text-base font-medium transition-colors",
291
+ "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
292
+ "border-l-4 border-transparent",
293
+ hasActiveChild &&
294
+ "bg-white/15 text-brand-secondary-foreground border-primary",
295
+ )}
296
+ >
297
+ <Icon
298
+ className={navIconCn(hasActiveChild)}
299
+ size={18}
300
+ strokeWidth={1.75}
301
+ />
302
+ <span className="flex-1 truncate text-left">{item.title}</span>
303
+ <ChevronDown
304
+ className={cn(
305
+ "ml-auto shrink-0 text-brand-secondary-foreground/40 transition-transform duration-200",
306
+ open && "rotate-180",
307
+ )}
308
+ size={14}
309
+ strokeWidth={2}
310
+ />
311
+ </button>
312
+
313
+ {open && item.subItems && (
314
+ <div className="ml-9 border-l border-white/15 pl-3">
315
+ {item.subItems.map((sub) => (
316
+ <button
317
+ key={sub.href}
318
+ type="button"
319
+ onClick={() => onNavigate?.(sub.href)}
320
+ className={cn(
321
+ "flex w-full items-center gap-2 py-1.5 pl-1 text-sm transition-colors",
322
+ "text-brand-secondary-foreground/50 hover:text-brand-secondary-foreground",
323
+ sub.isActive && "text-primary font-medium",
324
+ )}
325
+ >
326
+ <ChevronRight
327
+ size={11}
328
+ strokeWidth={2}
329
+ className={cn(
330
+ "shrink-0",
331
+ sub.isActive
332
+ ? "text-primary"
333
+ : "text-brand-secondary-foreground/30",
334
+ )}
335
+ />
336
+ <span className="truncate">{sub.title}</span>
337
+ </button>
338
+ ))}
339
+ </div>
340
+ )}
341
+ </div>
342
+ );
343
+ }
344
+
345
+ // ─── SidebarNav (root) ─────────────────────────────────────────────────────────
346
+
347
+ export function SidebarNav({
348
+ items,
349
+ userName = "Anonymous User",
350
+ collapsed = false,
351
+ logo,
352
+ logoCollapsed,
353
+ metricsGroups,
354
+ onNavigate,
355
+ onLogout,
356
+ onCollapsedChange,
357
+ className,
358
+ }: SidebarNavProps) {
359
+ const [userMenuOpen, setUserMenuOpen] = React.useState(false);
360
+
361
+ return (
362
+ <TooltipProvider>
363
+ <nav
364
+ data-slot="sidebar-nav"
365
+ data-collapsed={collapsed}
366
+ className={cn(
367
+ "flex h-full flex-col bg-brand-secondary text-brand-secondary-foreground",
368
+ "transition-all duration-200 ease-in-out",
369
+ collapsed ? "w-14" : "w-[279px]",
370
+ className,
371
+ )}
372
+ >
373
+ {/* Logo */}
374
+ {!collapsed && logo && (
375
+ <div className="flex items-center border-b border-white/15 px-5 py-4">
376
+ <img
377
+ src={logo}
378
+ alt="Logo"
379
+ className="h-8 w-auto object-contain object-left"
380
+ style={{ filter: "brightness(0) invert(1)" }}
381
+ />
382
+ </div>
383
+ )}
384
+ {collapsed && logoCollapsed && (
385
+ <div className="flex items-center justify-center border-b border-white/15 py-4">
386
+ <img
387
+ src={logoCollapsed}
388
+ alt="Logo"
389
+ className="h-8 w-8 object-contain"
390
+ style={{ filter: "brightness(0) invert(1)" }}
391
+ />
392
+ </div>
393
+ )}
394
+
395
+ {/* User section */}
396
+ <div className="border-b border-white/15">
397
+ <NavTooltip label={userName} collapsed={collapsed}>
398
+ <button
399
+ type="button"
400
+ onClick={() => !collapsed && setUserMenuOpen((prev) => !prev)}
401
+ className={cn(
402
+ "group flex w-full items-center gap-3 px-5 py-5 text-base transition-colors",
403
+ "text-brand-secondary-foreground hover:bg-white/10",
404
+ collapsed && "justify-center px-2 py-4",
405
+ )}
406
+ >
407
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center font-semibold text-xs bg-primary text-primary-foreground">
408
+ {getInitials(userName)}
409
+ </div>
410
+ {!collapsed && (
411
+ <>
412
+ <span className="flex-1 truncate text-left font-medium text-brand-secondary-foreground">
413
+ {userName}
414
+ </span>
415
+ <ChevronDown
416
+ className={cn(
417
+ "shrink-0 text-brand-secondary-foreground/50 transition-transform duration-200",
418
+ userMenuOpen && "rotate-180",
419
+ )}
420
+ size={16}
421
+ strokeWidth={2}
422
+ />
423
+ </>
424
+ )}
425
+ </button>
426
+ </NavTooltip>
427
+
428
+ {/* Logout dropdown */}
429
+ {!collapsed && userMenuOpen && (
430
+ <div className="border-t border-white/15 bg-black/20">
431
+ <button
432
+ type="button"
433
+ onClick={onLogout}
434
+ className={cn(
435
+ "flex w-full items-center gap-3 px-5 py-3 text-base",
436
+ "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground transition-colors",
437
+ )}
438
+ >
439
+ <LogOut
440
+ size={16}
441
+ strokeWidth={1.75}
442
+ className="shrink-0 text-destructive"
443
+ />
444
+ <span>Logout</span>
445
+ </button>
446
+ </div>
447
+ )}
448
+ </div>
449
+
450
+ {/* Financial metrics (Frontend sidebar only, hidden when collapsed) */}
451
+ {!collapsed && !!metricsGroups?.length && (
452
+ <div>
453
+ {metricsGroups.map((group, i) => (
454
+ <MetricsGroup key={i} group={group} />
455
+ ))}
456
+ </div>
457
+ )}
458
+
459
+ {/* Nav items */}
460
+ <div className="flex flex-col overflow-y-auto py-3">
461
+ {items.map((item) =>
462
+ item.isCollapsible ? (
463
+ <CollapsibleNavItem
464
+ key={item.href}
465
+ item={item}
466
+ collapsed={collapsed}
467
+ onNavigate={onNavigate}
468
+ />
469
+ ) : (
470
+ <SidebarNavItemView
471
+ key={item.href}
472
+ item={item}
473
+ collapsed={collapsed}
474
+ onNavigate={onNavigate}
475
+ />
476
+ ),
477
+ )}
478
+ </div>
479
+
480
+ {/* Expand/collapse toggle */}
481
+ {onCollapsedChange && (
482
+ <div className="mt-auto border-t border-white/15 bg-white/8">
483
+ <NavTooltip
484
+ label={collapsed ? "Expand" : "Collapse"}
485
+ collapsed={collapsed}
486
+ >
487
+ <button
488
+ type="button"
489
+ onClick={() => onCollapsedChange(!collapsed)}
490
+ className={cn(
491
+ "flex w-full items-center gap-3 px-3 py-3 transition-colors",
492
+ "text-brand-secondary-foreground/80 hover:bg-white/10 hover:text-brand-secondary-foreground",
493
+ collapsed && "justify-center px-2",
494
+ )}
495
+ >
496
+ {collapsed ? (
497
+ <PanelLeftOpen
498
+ size={18}
499
+ strokeWidth={1.75}
500
+ className="shrink-0"
501
+ />
502
+ ) : (
503
+ <PanelLeftClose
504
+ size={18}
505
+ strokeWidth={1.75}
506
+ className="shrink-0"
507
+ />
508
+ )}
509
+ {!collapsed && <span className="text-sm">Collapse</span>}
510
+ </button>
511
+ </NavTooltip>
512
+ </div>
513
+ )}
514
+ </nav>
515
+ </TooltipProvider>
516
+ );
517
+ }