@wealthx/shadcn 1.3.0 → 1.3.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.
- package/.turbo/turbo-build.log +53 -53
- package/CHANGELOG.md +12 -0
- package/dist/chunk-N6TNTQL6.mjs +446 -0
- package/dist/{chunk-FNQXOAYJ.mjs → chunk-Q2BGOAMG.mjs} +61 -28
- package/dist/components/ui/advisor-card.js +61 -28
- package/dist/components/ui/advisor-card.mjs +5 -3
- package/dist/components/ui/sidebar-nav.js +269 -176
- package/dist/components/ui/sidebar-nav.mjs +2 -1
- package/dist/index.js +516 -428
- package/dist/index.mjs +5 -3
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/index.tsx +2 -1
- package/src/components/ui/advisor-card.tsx +111 -54
- package/src/components/ui/sidebar-nav.tsx +195 -141
- package/src/styles/styles-css.ts +1 -1
- package/dist/chunk-ZC45IGZO.mjs +0 -388
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* - All icons must be Lucide icons (LucideIcon type).
|
|
11
11
|
* - Supports collapsible sub-items (accordion).
|
|
12
|
-
* - Collapsed state: icon-only, metrics
|
|
12
|
+
* - Collapsed state: icon-only, metrics animated out.
|
|
13
13
|
* - metricsGroups: optional financial summary rows (Frontend sidebar only).
|
|
14
14
|
* - No internal navigation — consumers wire onNavigate / onLogout.
|
|
15
15
|
*/
|
|
@@ -23,8 +23,10 @@ import {
|
|
|
23
23
|
PanelLeftOpen,
|
|
24
24
|
} from "lucide-react";
|
|
25
25
|
import type { LucideIcon } from "lucide-react";
|
|
26
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
26
27
|
import { cn } from "@/lib/utils";
|
|
27
28
|
import { formatCurrency } from "@/lib/format-currency";
|
|
29
|
+
import { Accordion, AccordionContent, AccordionItem } from "./accordion";
|
|
28
30
|
import { Button } from "./button";
|
|
29
31
|
import {
|
|
30
32
|
Tooltip,
|
|
@@ -108,7 +110,6 @@ function getInitials(name: string): string {
|
|
|
108
110
|
.slice(0, 2);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
|
|
112
113
|
function navIconCn(isActive: boolean): string {
|
|
113
114
|
return cn(
|
|
114
115
|
"shrink-0 transition-colors",
|
|
@@ -219,7 +220,7 @@ function SidebarNavItemView({
|
|
|
219
220
|
>
|
|
220
221
|
<Icon
|
|
221
222
|
className={navIconCn(item.isActive ?? false)}
|
|
222
|
-
size={
|
|
223
|
+
size={24}
|
|
223
224
|
strokeWidth={1.75}
|
|
224
225
|
/>
|
|
225
226
|
{!collapsed && <span className="truncate">{item.title}</span>}
|
|
@@ -264,7 +265,7 @@ function CollapsibleNavItem({
|
|
|
264
265
|
>
|
|
265
266
|
<Icon
|
|
266
267
|
className={navIconCn(hasActiveChild)}
|
|
267
|
-
size={
|
|
268
|
+
size={24}
|
|
268
269
|
strokeWidth={1.75}
|
|
269
270
|
/>
|
|
270
271
|
</Button>
|
|
@@ -273,65 +274,71 @@ function CollapsibleNavItem({
|
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
return (
|
|
276
|
-
<
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
className=
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
open && "rotate-180",
|
|
299
|
-
)}
|
|
300
|
-
size={14}
|
|
301
|
-
strokeWidth={2}
|
|
302
|
-
/>
|
|
303
|
-
</Button>
|
|
304
|
-
|
|
305
|
-
{open && item.subItems && (
|
|
306
|
-
<div className="ml-9 border-l border-white/15 pl-3">
|
|
307
|
-
{item.subItems.map((sub) => (
|
|
308
|
-
<Button
|
|
309
|
-
key={sub.href}
|
|
310
|
-
type="button"
|
|
311
|
-
variant="ghost"
|
|
312
|
-
onClick={() => onNavigate?.(sub.href)}
|
|
277
|
+
<Accordion
|
|
278
|
+
value={open ? [item.href] : []}
|
|
279
|
+
onValueChange={(values) => setOpen(values.length > 0)}
|
|
280
|
+
>
|
|
281
|
+
<AccordionItem className="border-none" value={item.href}>
|
|
282
|
+
<AccordionPrimitive.Header className="flex">
|
|
283
|
+
<AccordionPrimitive.Trigger
|
|
284
|
+
className={cn(
|
|
285
|
+
"group flex h-auto w-full items-center justify-start gap-3 px-3 py-2.5 text-base font-medium transition-colors",
|
|
286
|
+
"text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
|
|
287
|
+
"border-l-4 border-transparent",
|
|
288
|
+
hasActiveChild &&
|
|
289
|
+
"bg-white/15 text-brand-secondary-foreground border-primary",
|
|
290
|
+
)}
|
|
291
|
+
>
|
|
292
|
+
<Icon
|
|
293
|
+
className={navIconCn(hasActiveChild)}
|
|
294
|
+
size={24}
|
|
295
|
+
strokeWidth={1.75}
|
|
296
|
+
/>
|
|
297
|
+
<span className="flex-1 truncate text-left">{item.title}</span>
|
|
298
|
+
<ChevronDown
|
|
313
299
|
className={cn(
|
|
314
|
-
"
|
|
315
|
-
"
|
|
316
|
-
sub.isActive && "text-primary font-medium",
|
|
300
|
+
"ml-auto shrink-0 text-brand-secondary-foreground/40 transition-transform duration-200",
|
|
301
|
+
"group-data-[panel-open]:rotate-180",
|
|
317
302
|
)}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
303
|
+
size={14}
|
|
304
|
+
strokeWidth={2}
|
|
305
|
+
/>
|
|
306
|
+
</AccordionPrimitive.Trigger>
|
|
307
|
+
</AccordionPrimitive.Header>
|
|
308
|
+
|
|
309
|
+
{item.subItems && (
|
|
310
|
+
<AccordionContent className="p-0 text-inherit">
|
|
311
|
+
<div className="ml-9 border-l border-white/15 pl-3">
|
|
312
|
+
{item.subItems.map((sub) => (
|
|
313
|
+
<Button
|
|
314
|
+
key={sub.href}
|
|
315
|
+
type="button"
|
|
316
|
+
variant="ghost"
|
|
317
|
+
onClick={() => onNavigate?.(sub.href)}
|
|
318
|
+
className={cn(
|
|
319
|
+
"h-auto w-full justify-start gap-2 py-1.5 pl-1 text-sm transition-colors",
|
|
320
|
+
"text-brand-secondary-foreground/50 hover:text-brand-secondary-foreground",
|
|
321
|
+
sub.isActive && "text-primary font-medium",
|
|
322
|
+
)}
|
|
323
|
+
>
|
|
324
|
+
<ChevronRight
|
|
325
|
+
size={11}
|
|
326
|
+
strokeWidth={2}
|
|
327
|
+
className={cn(
|
|
328
|
+
"shrink-0",
|
|
329
|
+
sub.isActive
|
|
330
|
+
? "text-primary"
|
|
331
|
+
: "text-brand-secondary-foreground/30",
|
|
332
|
+
)}
|
|
333
|
+
/>
|
|
334
|
+
<span className="truncate">{sub.title}</span>
|
|
335
|
+
</Button>
|
|
336
|
+
))}
|
|
337
|
+
</div>
|
|
338
|
+
</AccordionContent>
|
|
339
|
+
)}
|
|
340
|
+
</AccordionItem>
|
|
341
|
+
</Accordion>
|
|
335
342
|
);
|
|
336
343
|
}
|
|
337
344
|
|
|
@@ -350,6 +357,27 @@ export function SidebarNav({
|
|
|
350
357
|
className,
|
|
351
358
|
}: SidebarNavProps) {
|
|
352
359
|
const [userMenuOpen, setUserMenuOpen] = React.useState(false);
|
|
360
|
+
const navScrollRef = React.useRef<HTMLDivElement>(null);
|
|
361
|
+
const expandedScrollRef = React.useRef(0);
|
|
362
|
+
|
|
363
|
+
React.useEffect(() => {
|
|
364
|
+
if (collapsed) setUserMenuOpen(false);
|
|
365
|
+
}, [collapsed]);
|
|
366
|
+
|
|
367
|
+
// Preserve nav items scroll position across collapse/expand transitions.
|
|
368
|
+
// Cleanup saves scrollTop before the DOM changes; setup restores it when expanding.
|
|
369
|
+
React.useLayoutEffect(() => {
|
|
370
|
+
const nav = navScrollRef.current;
|
|
371
|
+
if (!nav) return;
|
|
372
|
+
if (!collapsed) {
|
|
373
|
+
nav.scrollTop = expandedScrollRef.current;
|
|
374
|
+
}
|
|
375
|
+
return () => {
|
|
376
|
+
if (!collapsed && nav) {
|
|
377
|
+
expandedScrollRef.current = nav.scrollTop;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}, [collapsed]);
|
|
353
381
|
|
|
354
382
|
return (
|
|
355
383
|
<TooltipProvider>
|
|
@@ -357,105 +385,131 @@ export function SidebarNav({
|
|
|
357
385
|
data-slot="sidebar-nav"
|
|
358
386
|
data-collapsed={collapsed}
|
|
359
387
|
className={cn(
|
|
360
|
-
|
|
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",
|
|
388
|
+
"flex h-full flex-col bg-brand-secondary text-brand-secondary-foreground",
|
|
364
389
|
"transition-all duration-200 ease-in-out",
|
|
365
390
|
collapsed ? "w-14" : "w-[279px]",
|
|
366
391
|
className,
|
|
367
392
|
)}
|
|
368
393
|
>
|
|
369
|
-
{/* Logo */}
|
|
370
|
-
{
|
|
371
|
-
<div className="flex items-center border-b border-white/15
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
394
|
+
{/* Logo — crossfade between full and icon variant */}
|
|
395
|
+
{(logo || logoCollapsed) && (
|
|
396
|
+
<div className="relative flex items-center border-b border-white/15 py-4 overflow-hidden">
|
|
397
|
+
{logo && (
|
|
398
|
+
<img
|
|
399
|
+
src={logo}
|
|
400
|
+
alt="Logo"
|
|
401
|
+
className={cn(
|
|
402
|
+
"h-8 w-auto object-contain object-left px-5 transition-opacity duration-200",
|
|
403
|
+
collapsed ? "opacity-0" : "opacity-100",
|
|
404
|
+
)}
|
|
405
|
+
style={{ filter: "brightness(0) invert(1)" }}
|
|
406
|
+
/>
|
|
407
|
+
)}
|
|
408
|
+
{logoCollapsed && (
|
|
409
|
+
<img
|
|
410
|
+
src={logoCollapsed}
|
|
411
|
+
alt="Logo"
|
|
412
|
+
className={cn(
|
|
413
|
+
"absolute inset-y-0 left-0 right-0 m-auto h-8 w-8 object-contain transition-opacity duration-200",
|
|
414
|
+
collapsed ? "opacity-100" : "opacity-0",
|
|
415
|
+
)}
|
|
416
|
+
style={{ filter: "brightness(0) invert(1)" }}
|
|
417
|
+
/>
|
|
418
|
+
)}
|
|
378
419
|
</div>
|
|
379
420
|
)}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
421
|
+
|
|
422
|
+
{/* User section — crossfade between expanded and collapsed */}
|
|
423
|
+
<div className="relative border-b border-white/15">
|
|
424
|
+
{/* Expanded — in flow (defines height), no transition (overlay handles it) */}
|
|
425
|
+
<div
|
|
426
|
+
className={cn(
|
|
427
|
+
collapsed ? "opacity-0 pointer-events-none" : "opacity-100",
|
|
428
|
+
)}
|
|
429
|
+
>
|
|
430
|
+
<Accordion
|
|
431
|
+
value={userMenuOpen ? ["user-menu"] : []}
|
|
432
|
+
onValueChange={(values) => setUserMenuOpen(values.length > 0)}
|
|
433
|
+
>
|
|
434
|
+
<AccordionItem className="border-none" value="user-menu">
|
|
435
|
+
<AccordionPrimitive.Header className="flex">
|
|
436
|
+
<AccordionPrimitive.Trigger
|
|
437
|
+
className={cn(
|
|
438
|
+
"group flex h-auto w-full items-center justify-start gap-3 px-5 py-5 text-base transition-colors",
|
|
439
|
+
"text-brand-secondary-foreground hover:bg-white/10",
|
|
440
|
+
)}
|
|
441
|
+
>
|
|
442
|
+
<div className="flex h-8 w-8 shrink-0 items-center justify-center font-semibold text-xs bg-primary text-primary-foreground">
|
|
443
|
+
{getInitials(userName)}
|
|
444
|
+
</div>
|
|
445
|
+
<span className="flex-1 truncate text-left font-medium text-brand-secondary-foreground">
|
|
446
|
+
{userName}
|
|
447
|
+
</span>
|
|
448
|
+
<ChevronDown
|
|
449
|
+
className="ml-auto shrink-0 text-brand-secondary-foreground/50 transition-transform duration-200 group-data-[panel-open]:rotate-180"
|
|
450
|
+
size={16}
|
|
451
|
+
strokeWidth={2}
|
|
452
|
+
/>
|
|
453
|
+
</AccordionPrimitive.Trigger>
|
|
454
|
+
</AccordionPrimitive.Header>
|
|
455
|
+
|
|
456
|
+
<AccordionContent className="p-0 text-inherit">
|
|
457
|
+
<div className="border-t border-white/15 bg-black/20">
|
|
458
|
+
<Button
|
|
459
|
+
type="button"
|
|
460
|
+
variant="ghost"
|
|
461
|
+
onClick={onLogout}
|
|
462
|
+
className={cn(
|
|
463
|
+
"h-auto w-full justify-start gap-3 px-5 py-3 text-base",
|
|
464
|
+
"text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground transition-colors",
|
|
465
|
+
)}
|
|
466
|
+
>
|
|
467
|
+
<LogOut
|
|
468
|
+
size={16}
|
|
469
|
+
strokeWidth={1.75}
|
|
470
|
+
className="shrink-0 text-destructive"
|
|
471
|
+
/>
|
|
472
|
+
<span>Logout</span>
|
|
473
|
+
</Button>
|
|
474
|
+
</div>
|
|
475
|
+
</AccordionContent>
|
|
476
|
+
</AccordionItem>
|
|
477
|
+
</Accordion>
|
|
388
478
|
</div>
|
|
389
|
-
)}
|
|
390
479
|
|
|
391
|
-
|
|
392
|
-
<div className="border-b border-white/15">
|
|
480
|
+
{/* Collapsed — absolute overlay, centered avatar */}
|
|
393
481
|
<NavTooltip label={userName} collapsed={collapsed}>
|
|
394
|
-
<
|
|
395
|
-
type="button"
|
|
396
|
-
variant="ghost"
|
|
397
|
-
onClick={() => !collapsed && setUserMenuOpen((prev) => !prev)}
|
|
482
|
+
<div
|
|
398
483
|
className={cn(
|
|
399
|
-
"
|
|
400
|
-
"
|
|
401
|
-
collapsed && "justify-center px-2 py-4",
|
|
484
|
+
"absolute inset-0 flex items-center justify-center transition-opacity duration-200",
|
|
485
|
+
collapsed ? "opacity-100" : "opacity-0 pointer-events-none",
|
|
402
486
|
)}
|
|
403
487
|
>
|
|
404
488
|
<div className="flex h-8 w-8 shrink-0 items-center justify-center font-semibold text-xs bg-primary text-primary-foreground">
|
|
405
489
|
{getInitials(userName)}
|
|
406
490
|
</div>
|
|
407
|
-
{!collapsed && (
|
|
408
|
-
<>
|
|
409
|
-
<span className="flex-1 truncate text-left font-medium text-brand-secondary-foreground">
|
|
410
|
-
{userName}
|
|
411
|
-
</span>
|
|
412
|
-
<ChevronDown
|
|
413
|
-
className={cn(
|
|
414
|
-
"shrink-0 text-brand-secondary-foreground/50 transition-transform duration-200",
|
|
415
|
-
userMenuOpen && "rotate-180",
|
|
416
|
-
)}
|
|
417
|
-
size={16}
|
|
418
|
-
strokeWidth={2}
|
|
419
|
-
/>
|
|
420
|
-
</>
|
|
421
|
-
)}
|
|
422
|
-
</Button>
|
|
423
|
-
</NavTooltip>
|
|
424
|
-
|
|
425
|
-
{/* Logout dropdown */}
|
|
426
|
-
{!collapsed && userMenuOpen && (
|
|
427
|
-
<div className="border-t border-white/15 bg-black/20">
|
|
428
|
-
<Button
|
|
429
|
-
type="button"
|
|
430
|
-
variant="ghost"
|
|
431
|
-
onClick={onLogout}
|
|
432
|
-
className={cn(
|
|
433
|
-
"h-auto w-full justify-start gap-3 px-5 py-3 text-base",
|
|
434
|
-
"text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground transition-colors",
|
|
435
|
-
)}
|
|
436
|
-
>
|
|
437
|
-
<LogOut
|
|
438
|
-
size={16}
|
|
439
|
-
strokeWidth={1.75}
|
|
440
|
-
className="shrink-0 text-destructive"
|
|
441
|
-
/>
|
|
442
|
-
<span>Logout</span>
|
|
443
|
-
</Button>
|
|
444
491
|
</div>
|
|
445
|
-
|
|
492
|
+
</NavTooltip>
|
|
446
493
|
</div>
|
|
447
494
|
|
|
448
|
-
{/* Financial metrics
|
|
449
|
-
{
|
|
450
|
-
<
|
|
451
|
-
{
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
495
|
+
{/* Financial metrics — animated in/out with sidebar collapse to prevent nav items jumping */}
|
|
496
|
+
{!!metricsGroups?.length && (
|
|
497
|
+
<Accordion
|
|
498
|
+
value={!collapsed ? ["metrics"] : []}
|
|
499
|
+
onValueChange={() => {}}
|
|
500
|
+
>
|
|
501
|
+
<AccordionItem className="border-none" value="metrics">
|
|
502
|
+
<AccordionContent className="p-0 text-inherit">
|
|
503
|
+
{metricsGroups.map((group, i) => (
|
|
504
|
+
<MetricsGroup key={i} group={group} />
|
|
505
|
+
))}
|
|
506
|
+
</AccordionContent>
|
|
507
|
+
</AccordionItem>
|
|
508
|
+
</Accordion>
|
|
455
509
|
)}
|
|
456
510
|
|
|
457
511
|
{/* Nav items */}
|
|
458
|
-
<div className="flex flex-col overflow-y-auto py-3">
|
|
512
|
+
<div ref={navScrollRef} className="flex flex-col overflow-y-auto py-3">
|
|
459
513
|
{items.map((item) =>
|
|
460
514
|
item.isCollapsible ? (
|
|
461
515
|
<CollapsibleNavItem
|
|
@@ -487,20 +541,20 @@ export function SidebarNav({
|
|
|
487
541
|
variant="ghost"
|
|
488
542
|
onClick={() => onCollapsedChange(!collapsed)}
|
|
489
543
|
className={cn(
|
|
490
|
-
"h-
|
|
544
|
+
"h-12 w-full justify-start gap-3 px-3 py-3 transition-colors",
|
|
491
545
|
"text-brand-secondary-foreground/80 hover:bg-white/10 hover:text-brand-secondary-foreground",
|
|
492
546
|
collapsed && "justify-center px-2",
|
|
493
547
|
)}
|
|
494
548
|
>
|
|
495
549
|
{collapsed ? (
|
|
496
550
|
<PanelLeftOpen
|
|
497
|
-
size={
|
|
551
|
+
size={24}
|
|
498
552
|
strokeWidth={1.75}
|
|
499
553
|
className="shrink-0"
|
|
500
554
|
/>
|
|
501
555
|
) : (
|
|
502
556
|
<PanelLeftClose
|
|
503
|
-
size={
|
|
557
|
+
size={24}
|
|
504
558
|
strokeWidth={1.75}
|
|
505
559
|
className="shrink-0"
|
|
506
560
|
/>
|